From b621fcab50788cc51e6e014b5de11b4651554368 Mon Sep 17 00:00:00 2001 From: Justin Middler Date: Tue, 28 Jul 2026 17:37:20 +1000 Subject: [PATCH 1/6] chore(runtime): cut over to Node 26 across runtime and types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Coordinated Node 24 → 26 upgrade for engines, @types/node, CI, Compose mocks, Docker build image, and distroless runtime. Records support policy and closes the #34 rollup that Dependabot PRs #1 and #8 could not land alone. --- .github/workflows/ci.yml | 2 +- .github/workflows/security.yml | 2 +- Dockerfile | 4 +- README.md | 2 +- apps/agent-gateway/package.json | 2 +- apps/mcp-server/package.json | 2 +- apps/web/package.json | 2 +- apps/worker/package.json | 2 +- deploy/docker/docker-compose.homelab.yml | 8 +- docker-compose.yml | 6 +- docs/operations/node-26-upgrade-deferral.md | 54 ++++--- package.json | 4 +- packages/agent-harness/package.json | 2 +- packages/agents/package.json | 2 +- packages/audit/package.json | 2 +- packages/contracts/package.json | 2 +- packages/database/package.json | 2 +- packages/event-protocol/package.json | 2 +- packages/integrations/package.json | 2 +- packages/mcp/package.json | 2 +- pnpm-lock.yaml | 148 +++++++++++--------- 21 files changed, 137 insertions(+), 117 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 19d7c9b..0a5cb50 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,7 +48,7 @@ jobs: version: 11.17.0 - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: - node-version: "24" + node-version: "26" cache: pnpm - name: Start object storage run: | diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index a79ec2e..7d407c5 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -33,7 +33,7 @@ jobs: version: 11.17.0 - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: - node-version: "24" + node-version: "26" cache: pnpm - run: pnpm install --frozen-lockfile - run: pnpm audit --audit-level high diff --git a/Dockerfile b/Dockerfile index 5f5bccc..3cdc9da 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1.7 -FROM node:24-bookworm-slim AS build +FROM node:26-bookworm-slim AS build ENV PNPM_HOME=/pnpm ENV PATH=$PNPM_HOME:$PATH RUN corepack enable @@ -15,7 +15,7 @@ RUN pnpm deploy --filter=@muster/worker --prod /prod/worker \ && cp -R /workspace/apps/web/.next/static /workspace/apps/web/.next/standalone/apps/web/.next/static \ && cp -R /workspace/apps/web/public /workspace/apps/web/.next/standalone/apps/web/public -FROM gcr.io/distroless/nodejs24-debian13:nonroot AS runtime +FROM gcr.io/distroless/nodejs26-debian13:nonroot AS runtime ENV NODE_ENV=production WORKDIR /app COPY --from=build --chown=nonroot:nonroot /workspace/apps/web/.next/standalone ./web diff --git a/README.md b/README.md index 7155219..c2575a8 100644 --- a/README.md +++ b/README.md @@ -275,7 +275,7 @@ harnesses are upcoming work tracked in [#33](https://github.com/jusso-dev/Muster ## Testing and contribution checks -For source tests, use Node 24+, pnpm 11.17.0, Docker, and installed Chromium +For source tests, use Node 26+, pnpm 11.17.0, Docker, and installed Chromium browser dependencies. The application, worker, and gateway test servers are started by Playwright; PostgreSQL and Redis are the required local services. diff --git a/apps/agent-gateway/package.json b/apps/agent-gateway/package.json index b9eac69..ac6cbde 100644 --- a/apps/agent-gateway/package.json +++ b/apps/agent-gateway/package.json @@ -23,7 +23,7 @@ "zod": "4.4.3" }, "devDependencies": { - "@types/node": "^24.0.0", + "@types/node": "^26.1.1", "tsx": "^4.20.6", "typescript": "catalog:", "vitest": "4.1.10" diff --git a/apps/mcp-server/package.json b/apps/mcp-server/package.json index 764d143..316f3d8 100644 --- a/apps/mcp-server/package.json +++ b/apps/mcp-server/package.json @@ -20,7 +20,7 @@ "zod": "4.4.3" }, "devDependencies": { - "@types/node": "^24.0.0", + "@types/node": "^26.1.1", "tsx": "^4.20.6", "typescript": "catalog:", "vitest": "4.1.10" diff --git a/apps/web/package.json b/apps/web/package.json index 9caaea3..15d5d36 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -50,7 +50,7 @@ "@muster/mcp": "workspace:*" }, "devDependencies": { - "@types/node": "^24.0.0", + "@types/node": "^26.1.1", "@types/react": "^19.1.16", "@types/react-dom": "^19.1.9", "typescript": "catalog:", diff --git a/apps/worker/package.json b/apps/worker/package.json index 9694ed9..2598d4c 100644 --- a/apps/worker/package.json +++ b/apps/worker/package.json @@ -27,7 +27,7 @@ "zod": "4.4.3" }, "devDependencies": { - "@types/node": "^24.0.0", + "@types/node": "^26.1.1", "@types/nodemailer": "^7.0.1", "tsx": "^4.20.6", "typescript": "catalog:", diff --git a/deploy/docker/docker-compose.homelab.yml b/deploy/docker/docker-compose.homelab.yml index 51a78d8..6e46c6a 100644 --- a/deploy/docker/docker-compose.homelab.yml +++ b/deploy/docker/docker-compose.homelab.yml @@ -196,7 +196,7 @@ services: mock-kelpie: profiles: ["mock"] - image: node:24-bookworm-slim + image: node:26-bookworm-slim restart: unless-stopped working_dir: /mock command: ["node", "mock.mjs"] @@ -208,7 +208,7 @@ services: mock-tawny: profiles: ["mock"] - image: node:24-bookworm-slim + image: node:26-bookworm-slim restart: unless-stopped working_dir: /mock command: ["node", "mock.mjs"] @@ -220,7 +220,7 @@ services: mock-bower: profiles: ["mock"] - image: node:24-bookworm-slim + image: node:26-bookworm-slim restart: unless-stopped working_dir: /mock command: ["node", "mock.mjs"] @@ -232,7 +232,7 @@ services: mock-connector: profiles: ["mock"] - image: node:24-bookworm-slim + image: node:26-bookworm-slim restart: unless-stopped working_dir: /mock command: ["node", "mock.mjs"] diff --git a/docker-compose.yml b/docker-compose.yml index 801bf90..8502365 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -174,7 +174,7 @@ services: tty: true mock-kelpie: - image: node:24-bookworm-slim + image: node:26-bookworm-slim working_dir: /mock command: ["node", "mock.mjs"] environment: @@ -186,7 +186,7 @@ services: - "4011:4011" mock-tawny: - image: node:24-bookworm-slim + image: node:26-bookworm-slim working_dir: /mock command: ["node", "mock.mjs"] environment: @@ -198,7 +198,7 @@ services: - "4012:4012" mock-bower: - image: node:24-bookworm-slim + image: node:26-bookworm-slim working_dir: /mock command: ["node", "mock.mjs"] environment: diff --git a/docs/operations/node-26-upgrade-deferral.md b/docs/operations/node-26-upgrade-deferral.md index 0236158..bd94517 100644 --- a/docs/operations/node-26-upgrade-deferral.md +++ b/docs/operations/node-26-upgrade-deferral.md @@ -1,32 +1,40 @@ -# Node 26 runtime upgrade deferral (#34) +# Node runtime support policy -Tracker #78 item 10 defers the coordinated Node 26 runtime/type cutover until -product gates for the Hermes MCP pivot are stable. +## Supported runtime -## Status: deferred +Muster's supported production and CI Node.js major is **26**. -Product gates landed (or in progress) under #78: +| Surface | Policy | +| --- | --- | +| `package.json` `engines.node` | `>=26.0.0` (enforced via `.npmrc` `engine-strict=true`) | +| CI / security workflows | `actions/setup-node` with `node-version: "26"` | +| Docker build stage | `node:26-bookworm-slim` | +| Distroless runtime | `gcr.io/distroless/nodejs26-debian13:nonroot` | +| Compose mock images | `node:26-bookworm-slim` | +| Type definitions | `@types/node` `^26.1.1` (aligned with runtime major) | -- Remote MCP vertical slice (#72) -- Write/proposal tools + approvals -- Operational knowledge (#71) -- Skill packs (#73) -- Observability (#77) -- Governed missions (#76) -- Packaging/admin minimum +Do not advance `@types/node` ahead of the production runtime major, and do not +change only one of Dockerfile / distroless / CI / Compose / engines in isolation. -The Node 26 matrix in #34 remains blocked until: +## Upgrade history -1. Node 26 is confirmed as the supported production/LTS target for Muster. -2. Docker build image, distroless runtime, CI, Compose mocks, `engines`, and - `@types/node` move together with a frozen lockfile. -3. Full `pnpm check`, migrate/bootstrap, unit/integration, Playwright, and - container security scans pass under Node 26. +- **#34** — coordinated cutover from Node 24 → Node 26 (Docker, distroless, + CI, Compose, engines, `@types/node`, lockfile). +- Supersedes Dependabot PRs #1 and #8, which were intentionally not merged alone. -Do not merge Dependabot PRs #1 / #8 in isolation. +## Rollback -## Revisit trigger +Rollback is the previous GHCR image built from the last Node 24 multi-stage +Dockerfile (`node:24-bookworm-slim` + `gcr.io/distroless/nodejs24-debian13:nonroot`). +Redeploy that digest and restore the matching git revision if a Node 26 image +fails health checks. -Open a replacement rollup PR against #34 only after the above gates are green -on Node 24 in production-shaped environments and the support policy is written -down in this document. +## Compatibility gates (release checklist) + +- [ ] `pnpm install --frozen-lockfile` on Node 26 +- [ ] `pnpm check` (lint, typecheck, test, build) +- [ ] `pnpm db:migrate` / `db:bootstrap` / `db:verify-clean` as applicable +- [ ] Unit + integration suites under Node 26 CI +- [ ] Playwright / Chromium e2e where required by the release +- [ ] `linux/amd64` image build; web / worker / agent-gateway health +- [ ] `pnpm audit --audit-level high`, licence/SBOM/container scans, CodeQL diff --git a/package.json b/package.json index 20d167f..06d89dd 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "license": "Apache-2.0", "packageManager": "pnpm@11.17.0", "engines": { - "node": ">=24.0.0" + "node": ">=26.0.0" }, "scripts": { "build": "turbo build", @@ -37,7 +37,7 @@ "devDependencies": { "@axe-core/playwright": "^4.12.1", "@playwright/test": "1.62.0", - "@types/node": "^24.0.0", + "@types/node": "^26.1.1", "prettier": "^3.6.2", "turbo": "2.10.7", "typescript": "7.0.2", diff --git a/packages/agent-harness/package.json b/packages/agent-harness/package.json index 9df9104..78eb10a 100644 --- a/packages/agent-harness/package.json +++ b/packages/agent-harness/package.json @@ -53,7 +53,7 @@ "zod": "4.4.3" }, "devDependencies": { - "@types/node": "^24.0.0", + "@types/node": "^26.1.1", "typescript": "catalog:", "vitest": "4.1.10" } diff --git a/packages/agents/package.json b/packages/agents/package.json index 5609809..4c6a112 100644 --- a/packages/agents/package.json +++ b/packages/agents/package.json @@ -22,7 +22,7 @@ "zod": "4.4.3" }, "devDependencies": { - "@types/node": "^24.0.0", + "@types/node": "^26.1.1", "typescript": "catalog:", "vitest": "4.1.10" } diff --git a/packages/audit/package.json b/packages/audit/package.json index a4739f7..660c97a 100644 --- a/packages/audit/package.json +++ b/packages/audit/package.json @@ -17,7 +17,7 @@ "test": "vitest run" }, "devDependencies": { - "@types/node": "^24.0.0", + "@types/node": "^26.1.1", "typescript": "catalog:", "vitest": "4.1.10" } diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 4bc838e..f996791 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -21,7 +21,7 @@ "zod": "4.4.3" }, "devDependencies": { - "@types/node": "^24.0.0", + "@types/node": "^26.1.1", "tsx": "^4.20.6", "typescript": "catalog:", "vitest": "4.1.10" diff --git a/packages/database/package.json b/packages/database/package.json index b389f4a..42445bb 100644 --- a/packages/database/package.json +++ b/packages/database/package.json @@ -43,7 +43,7 @@ "zod": "4.4.3" }, "devDependencies": { - "@types/node": "^24.0.0", + "@types/node": "^26.1.1", "@types/pg": "^8.15.5", "drizzle-kit": "0.31.10", "tsx": "^4.20.6", diff --git a/packages/event-protocol/package.json b/packages/event-protocol/package.json index f6f55ba..7e4cda0 100644 --- a/packages/event-protocol/package.json +++ b/packages/event-protocol/package.json @@ -20,7 +20,7 @@ "@muster/contracts": "workspace:*" }, "devDependencies": { - "@types/node": "^24.0.0", + "@types/node": "^26.1.1", "typescript": "catalog:", "vitest": "4.1.10" } diff --git a/packages/integrations/package.json b/packages/integrations/package.json index b28fa94..1184de4 100644 --- a/packages/integrations/package.json +++ b/packages/integrations/package.json @@ -21,7 +21,7 @@ "zod": "4.4.3" }, "devDependencies": { - "@types/node": "^24.0.0", + "@types/node": "^26.1.1", "typescript": "catalog:", "vitest": "4.1.10" } diff --git a/packages/mcp/package.json b/packages/mcp/package.json index 43359b4..9d8a375 100644 --- a/packages/mcp/package.json +++ b/packages/mcp/package.json @@ -30,7 +30,7 @@ "zod": "4.4.3" }, "devDependencies": { - "@types/node": "^24.0.0", + "@types/node": "^26.1.1", "tsx": "^4.20.6", "typescript": "catalog:", "vitest": "4.1.10" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 31ed7b0..fb49206 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,8 +30,8 @@ importers: specifier: 1.62.0 version: 1.62.0 '@types/node': - specifier: ^24.0.0 - version: 24.13.3 + specifier: ^26.1.1 + version: 26.1.1 prettier: specifier: ^3.6.2 version: 3.9.6 @@ -43,7 +43,7 @@ importers: version: 7.0.2 vitest: specifier: 4.1.10 - version: 4.1.10(@types/node@24.13.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@types/node@26.1.1)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) apps/agent-gateway: dependencies: @@ -76,8 +76,8 @@ importers: version: 4.4.3 devDependencies: '@types/node': - specifier: ^24.0.0 - version: 24.13.3 + specifier: ^26.1.1 + version: 26.1.1 tsx: specifier: ^4.20.6 version: 4.23.1 @@ -86,7 +86,7 @@ importers: version: 7.0.2 vitest: specifier: 4.1.10 - version: 4.1.10(@types/node@24.13.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@types/node@26.1.1)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) apps/mcp-server: dependencies: @@ -110,8 +110,8 @@ importers: version: 4.4.3 devDependencies: '@types/node': - specifier: ^24.0.0 - version: 24.13.3 + specifier: ^26.1.1 + version: 26.1.1 tsx: specifier: ^4.20.6 version: 4.23.1 @@ -120,7 +120,7 @@ importers: version: 7.0.2 vitest: specifier: 4.1.10 - version: 4.1.10(@types/node@24.13.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@types/node@26.1.1)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) apps/web: dependencies: @@ -189,7 +189,7 @@ importers: version: 3.29.0 better-auth: specifier: 1.6.25 - version: 1.6.25(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@types/pg@8.20.0)(kysely@0.29.4)(pg@8.22.0))(next@16.2.12(@playwright/test@1.62.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(pg@8.22.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vitest@4.1.10(@types/node@24.13.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0))) + version: 1.6.25(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@types/pg@8.20.0)(kysely@0.29.4)(pg@8.22.0))(next@16.2.12(@playwright/test@1.62.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(pg@8.22.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vitest@4.1.10(@types/node@26.1.1)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0))) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -234,8 +234,8 @@ importers: version: 4.4.3 devDependencies: '@types/node': - specifier: ^24.0.0 - version: 24.13.3 + specifier: ^26.1.1 + version: 26.1.1 '@types/react': specifier: ^19.1.16 version: 19.2.17 @@ -247,7 +247,7 @@ importers: version: 7.0.2 vitest: specifier: 4.1.10 - version: 4.1.10(@types/node@24.13.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@types/node@26.1.1)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) apps/worker: dependencies: @@ -292,8 +292,8 @@ importers: version: 4.4.3 devDependencies: '@types/node': - specifier: ^24.0.0 - version: 24.13.3 + specifier: ^26.1.1 + version: 26.1.1 '@types/nodemailer': specifier: ^7.0.1 version: 7.0.12 @@ -305,7 +305,7 @@ importers: version: 7.0.2 vitest: specifier: 4.1.10 - version: 4.1.10(@types/node@24.13.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@types/node@26.1.1)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/agent-harness: dependencies: @@ -335,14 +335,14 @@ importers: version: 4.4.3 devDependencies: '@types/node': - specifier: ^24.0.0 - version: 24.13.3 + specifier: ^26.1.1 + version: 26.1.1 typescript: specifier: 'catalog:' version: 7.0.2 vitest: specifier: 4.1.10 - version: 4.1.10(@types/node@24.13.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@types/node@26.1.1)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/agents: dependencies: @@ -357,14 +357,14 @@ importers: version: 4.4.3 devDependencies: '@types/node': - specifier: ^24.0.0 - version: 24.13.3 + specifier: ^26.1.1 + version: 26.1.1 typescript: specifier: 'catalog:' version: 7.0.2 vitest: specifier: 4.1.10 - version: 4.1.10(@types/node@24.13.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@types/node@26.1.1)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/alerts: dependencies: @@ -386,7 +386,7 @@ importers: version: 7.0.2 vitest: specifier: 4.1.10 - version: 4.1.10(@types/node@24.13.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@types/node@26.1.1)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/api-client: dependencies: @@ -399,19 +399,19 @@ importers: version: 7.0.2 vitest: specifier: 4.1.10 - version: 4.1.10(@types/node@24.13.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@types/node@26.1.1)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/audit: devDependencies: '@types/node': - specifier: ^24.0.0 - version: 24.13.3 + specifier: ^26.1.1 + version: 26.1.1 typescript: specifier: 'catalog:' version: 7.0.2 vitest: specifier: 4.1.10 - version: 4.1.10(@types/node@24.13.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@types/node@26.1.1)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/auth: dependencies: @@ -420,13 +420,13 @@ importers: version: 1.6.25(@better-auth/core@1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1))(@better-auth/utils@0.4.2)(drizzle-orm@0.45.2(@types/pg@8.20.0)(kysely@0.29.4)(pg@8.22.0)) '@better-auth/passkey': specifier: 1.6.25 - version: 1.6.25(@better-auth/core@1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-auth@1.6.25(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@types/pg@8.20.0)(kysely@0.29.4)(pg@8.22.0))(next@16.2.12(@playwright/test@1.62.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(pg@8.22.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vitest@4.1.10(@types/node@24.13.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0))))(better-call@1.3.7(zod@4.4.3))(nanostores@1.4.1) + version: 1.6.25(@better-auth/core@1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-auth@1.6.25(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@types/pg@8.20.0)(kysely@0.29.4)(pg@8.22.0))(next@16.2.12(@playwright/test@1.62.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(pg@8.22.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vitest@4.1.10(@types/node@26.1.1)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0))))(better-call@1.3.7(zod@4.4.3))(nanostores@1.4.1) '@muster/database': specifier: workspace:* version: link:../database better-auth: specifier: 1.6.25 - version: 1.6.25(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@types/pg@8.20.0)(kysely@0.29.4)(pg@8.22.0))(next@16.2.12(@playwright/test@1.62.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(pg@8.22.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vitest@4.1.10(@types/node@24.13.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0))) + version: 1.6.25(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@types/pg@8.20.0)(kysely@0.29.4)(pg@8.22.0))(next@16.2.12(@playwright/test@1.62.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(pg@8.22.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vitest@4.1.10(@types/node@26.1.1)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0))) nodemailer: specifier: 9.0.3 version: 9.0.3 @@ -439,7 +439,7 @@ importers: version: 7.0.2 vitest: specifier: 4.1.10 - version: 4.1.10(@types/node@24.13.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@types/node@26.1.1)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/authz: devDependencies: @@ -448,7 +448,7 @@ importers: version: 7.0.2 vitest: specifier: 4.1.10 - version: 4.1.10(@types/node@24.13.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@types/node@26.1.1)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/config: dependencies: @@ -464,7 +464,7 @@ importers: version: 7.0.2 vitest: specifier: 4.1.10 - version: 4.1.10(@types/node@24.13.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@types/node@26.1.1)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/contracts: dependencies: @@ -473,8 +473,8 @@ importers: version: 4.4.3 devDependencies: '@types/node': - specifier: ^24.0.0 - version: 24.13.3 + specifier: ^26.1.1 + version: 26.1.1 tsx: specifier: ^4.20.6 version: 4.23.1 @@ -483,7 +483,7 @@ importers: version: 7.0.2 vitest: specifier: 4.1.10 - version: 4.1.10(@types/node@24.13.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@types/node@26.1.1)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/database: dependencies: @@ -513,8 +513,8 @@ importers: version: 4.4.3 devDependencies: '@types/node': - specifier: ^24.0.0 - version: 24.13.3 + specifier: ^26.1.1 + version: 26.1.1 '@types/pg': specifier: ^8.15.5 version: 8.20.0 @@ -529,7 +529,7 @@ importers: version: 7.0.2 vitest: specifier: 4.1.10 - version: 4.1.10(@types/node@24.13.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@types/node@26.1.1)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/event-protocol: dependencies: @@ -538,14 +538,14 @@ importers: version: link:../contracts devDependencies: '@types/node': - specifier: ^24.0.0 - version: 24.13.3 + specifier: ^26.1.1 + version: 26.1.1 typescript: specifier: 'catalog:' version: 7.0.2 vitest: specifier: 4.1.10 - version: 4.1.10(@types/node@24.13.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@types/node@26.1.1)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/evidence: dependencies: @@ -558,7 +558,7 @@ importers: version: 7.0.2 vitest: specifier: 4.1.10 - version: 4.1.10(@types/node@24.13.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@types/node@26.1.1)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/integrations: dependencies: @@ -570,14 +570,14 @@ importers: version: 4.4.3 devDependencies: '@types/node': - specifier: ^24.0.0 - version: 24.13.3 + specifier: ^26.1.1 + version: 26.1.1 typescript: specifier: 'catalog:' version: 7.0.2 vitest: specifier: 4.1.10 - version: 4.1.10(@types/node@24.13.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@types/node@26.1.1)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/investigations: dependencies: @@ -602,7 +602,7 @@ importers: version: 7.0.2 vitest: specifier: 4.1.10 - version: 4.1.10(@types/node@24.13.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@types/node@26.1.1)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/mcp: dependencies: @@ -635,8 +635,8 @@ importers: version: 4.4.3 devDependencies: '@types/node': - specifier: ^24.0.0 - version: 24.13.3 + specifier: ^26.1.1 + version: 26.1.1 tsx: specifier: ^4.20.6 version: 4.23.1 @@ -645,7 +645,7 @@ importers: version: 7.0.2 vitest: specifier: 4.1.10 - version: 4.1.10(@types/node@24.13.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@types/node@26.1.1)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/notifications: dependencies: @@ -658,7 +658,7 @@ importers: version: 7.0.2 vitest: specifier: 4.1.10 - version: 4.1.10(@types/node@24.13.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@types/node@26.1.1)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/rooms: dependencies: @@ -686,7 +686,7 @@ importers: version: 7.0.2 vitest: specifier: 4.1.10 - version: 4.1.10(@types/node@24.13.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@types/node@26.1.1)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/search: dependencies: @@ -702,7 +702,7 @@ importers: version: 7.0.2 vitest: specifier: 4.1.10 - version: 4.1.10(@types/node@24.13.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@types/node@26.1.1)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/test-utils: dependencies: @@ -715,7 +715,7 @@ importers: version: 7.0.2 vitest: specifier: 4.1.10 - version: 4.1.10(@types/node@24.13.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@types/node@26.1.1)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/ui: devDependencies: @@ -724,7 +724,7 @@ importers: version: 7.0.2 vitest: specifier: 4.1.10 - version: 4.1.10(@types/node@24.13.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@types/node@26.1.1)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages/workflows: dependencies: @@ -740,7 +740,7 @@ importers: version: 7.0.2 vitest: specifier: 4.1.10 - version: 4.1.10(@types/node@24.13.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.10(@types/node@26.1.1)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) packages: @@ -2882,6 +2882,9 @@ packages: '@types/node@24.13.3': resolution: {integrity: sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==} + '@types/node@26.1.1': + resolution: {integrity: sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==} + '@types/nodemailer@7.0.12': resolution: {integrity: sha512-80vKwiIsVSyFA1rRovH59jNPLBOuc6dRZIHEu40gXTkBkZnQv8vog1xSGEb9j5q/tdMAs5ivvDR2pLTU0hGHXA==} @@ -4320,6 +4323,9 @@ packages: undici-types@7.18.2: resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} + undici-types@8.3.0: + resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==} + unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} @@ -4522,14 +4528,14 @@ snapshots: '@better-auth/core': 1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1) '@better-auth/utils': 0.4.2 - '@better-auth/passkey@1.6.25(@better-auth/core@1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-auth@1.6.25(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@types/pg@8.20.0)(kysely@0.29.4)(pg@8.22.0))(next@16.2.12(@playwright/test@1.62.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(pg@8.22.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vitest@4.1.10(@types/node@24.13.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0))))(better-call@1.3.7(zod@4.4.3))(nanostores@1.4.1)': + '@better-auth/passkey@1.6.25(@better-auth/core@1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-auth@1.6.25(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@types/pg@8.20.0)(kysely@0.29.4)(pg@8.22.0))(next@16.2.12(@playwright/test@1.62.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(pg@8.22.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vitest@4.1.10(@types/node@26.1.1)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0))))(better-call@1.3.7(zod@4.4.3))(nanostores@1.4.1)': dependencies: '@better-auth/core': 1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1) '@better-auth/utils': 0.4.2 '@better-fetch/fetch': 1.3.1 '@simplewebauthn/browser': 13.3.0 '@simplewebauthn/server': 13.3.2 - better-auth: 1.6.25(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@types/pg@8.20.0)(kysely@0.29.4)(pg@8.22.0))(next@16.2.12(@playwright/test@1.62.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(pg@8.22.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vitest@4.1.10(@types/node@24.13.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0))) + better-auth: 1.6.25(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@types/pg@8.20.0)(kysely@0.29.4)(pg@8.22.0))(next@16.2.12(@playwright/test@1.62.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(pg@8.22.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vitest@4.1.10(@types/node@26.1.1)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0))) better-call: 1.3.7(zod@4.4.3) nanostores: 1.4.1 zod: 4.4.3 @@ -6330,9 +6336,13 @@ snapshots: dependencies: undici-types: 7.18.2 + '@types/node@26.1.1': + dependencies: + undici-types: 8.3.0 + '@types/nodemailer@7.0.12': dependencies: - '@types/node': 24.13.3 + '@types/node': 26.1.1 '@types/pg@8.20.0': dependencies: @@ -6419,13 +6429,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.10(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0))': + '@vitest/mocker@4.1.10(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0))': dependencies: '@vitest/spy': 4.1.10 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0) + vite: 8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0) '@vitest/pretty-format@4.1.10': dependencies: @@ -6483,7 +6493,7 @@ snapshots: baseline-browser-mapping@2.11.1: {} - better-auth@1.6.25(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@types/pg@8.20.0)(kysely@0.29.4)(pg@8.22.0))(next@16.2.12(@playwright/test@1.62.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(pg@8.22.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vitest@4.1.10(@types/node@24.13.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0))): + better-auth@1.6.25(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@types/pg@8.20.0)(kysely@0.29.4)(pg@8.22.0))(next@16.2.12(@playwright/test@1.62.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(pg@8.22.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vitest@4.1.10(@types/node@26.1.1)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0))): dependencies: '@better-auth/core': 1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1) '@better-auth/drizzle-adapter': 1.6.25(@better-auth/core@1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1))(@better-auth/utils@0.4.2)(drizzle-orm@0.45.2(@types/pg@8.20.0)(kysely@0.29.4)(pg@8.22.0)) @@ -6509,7 +6519,7 @@ snapshots: pg: 8.22.0 react: 19.2.8 react-dom: 19.2.8(react@19.2.8) - vitest: 4.1.10(@types/node@24.13.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + vitest: 4.1.10(@types/node@26.1.1)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) transitivePeerDependencies: - '@cloudflare/workers-types' - '@opentelemetry/api' @@ -7713,6 +7723,8 @@ snapshots: undici-types@7.18.2: {} + undici-types@8.3.0: {} + unpipe@1.0.0: {} use-callback-ref@1.3.3(@types/react@19.2.17)(react@19.2.8): @@ -7755,7 +7767,7 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 - vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0): + vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0): dependencies: lightningcss: 1.33.0 picomatch: 4.0.5 @@ -7763,17 +7775,17 @@ snapshots: rolldown: 1.1.5 tinyglobby: 0.2.17 optionalDependencies: - '@types/node': 24.13.3 + '@types/node': 26.1.1 esbuild: 0.28.1 fsevents: 2.3.3 jiti: 2.7.0 tsx: 4.23.1 yaml: 2.9.0 - vitest@4.1.10(@types/node@24.13.3)(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)): + vitest@4.1.10(@types/node@26.1.1)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.10 - '@vitest/mocker': 4.1.10(vite@8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) + '@vitest/mocker': 4.1.10(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0)) '@vitest/pretty-format': 4.1.10 '@vitest/runner': 4.1.10 '@vitest/snapshot': 4.1.10 @@ -7790,10 +7802,10 @@ snapshots: tinyexec: 1.2.4 tinyglobby: 0.2.17 tinyrainbow: 3.1.0 - vite: 8.1.5(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0) + vite: 8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 24.13.3 + '@types/node': 26.1.1 transitivePeerDependencies: - msw From 3208b0ecc8d561c9806a07357f8b15d95093d1d9 Mon Sep 17 00:00:00 2001 From: Justin Middler Date: Tue, 28 Jul 2026 17:41:12 +1000 Subject: [PATCH 2/6] fix(ci): Node 26 corepack install and drizzle migration snapshots Official node:26-bookworm-slim no longer ships corepack on PATH. Replace hand-written migrations 0022/0023 with drizzle-kit output so the committed-migrations CI check stays clean. --- Dockerfile | 3 +- ...ed_missions.sql => 0022_messy_proteus.sql} | 71 +- .../migrations/0022_operational_knowledge.sql | 33 - .../migrations/meta/0022_snapshot.json | 13460 ++++++++++++++++ .../database/migrations/meta/_journal.json | 13 +- 5 files changed, 13517 insertions(+), 63 deletions(-) rename packages/database/migrations/{0023_governed_missions.sql => 0022_messy_proteus.sql} (51%) delete mode 100644 packages/database/migrations/0022_operational_knowledge.sql create mode 100644 packages/database/migrations/meta/0022_snapshot.json diff --git a/Dockerfile b/Dockerfile index 3cdc9da..3f88fcd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,8 @@ FROM node:26-bookworm-slim AS build ENV PNPM_HOME=/pnpm ENV PATH=$PNPM_HOME:$PATH -RUN corepack enable +# Node 26 official slim images no longer ship corepack on PATH by default. +RUN npm install -g corepack@latest && corepack enable WORKDIR /workspace COPY . . RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile diff --git a/packages/database/migrations/0023_governed_missions.sql b/packages/database/migrations/0022_messy_proteus.sql similarity index 51% rename from packages/database/migrations/0023_governed_missions.sql rename to packages/database/migrations/0022_messy_proteus.sql index 175ae9d..d1bf42e 100644 --- a/packages/database/migrations/0023_governed_missions.sql +++ b/packages/database/migrations/0022_messy_proteus.sql @@ -1,3 +1,19 @@ +CREATE TABLE "governed_mission_runs" ( + "id" uuid PRIMARY KEY NOT NULL, + "organisation_id" uuid NOT NULL, + "mission_id" uuid NOT NULL, + "idempotency_key" text NOT NULL, + "status" text DEFAULT 'accepted' NOT NULL, + "hermes_installation_id" uuid, + "hermes_profile" text, + "initiating_actor_id" uuid NOT NULL, + "delivery_evidence" jsonb DEFAULT '{}'::jsonb NOT NULL, + "error" text, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "governed_mission_runs_status_check" CHECK ("governed_mission_runs"."status" in ('accepted','blocked','running','succeeded','failed','cancelled')) +); +--> statement-breakpoint CREATE TABLE "governed_missions" ( "id" uuid PRIMARY KEY NOT NULL, "organisation_id" uuid NOT NULL, @@ -12,35 +28,52 @@ CREATE TABLE "governed_missions" ( "updated_by_actor_id" uuid, "kill_switch" boolean DEFAULT false NOT NULL, "created_at" timestamp with time zone DEFAULT now() NOT NULL, - "updated_at" timestamp with time zone DEFAULT now() NOT NULL + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "governed_missions_status_check" CHECK ("governed_missions"."status" in ('active','paused','cancelled','archived')) ); --> statement-breakpoint -CREATE TABLE "governed_mission_runs" ( +CREATE TABLE "operational_knowledge" ( "id" uuid PRIMARY KEY NOT NULL, "organisation_id" uuid NOT NULL, - "mission_id" uuid NOT NULL, - "idempotency_key" text NOT NULL, - "status" text DEFAULT 'accepted' NOT NULL, - "hermes_installation_id" uuid, - "hermes_profile" text, - "initiating_actor_id" uuid NOT NULL, - "delivery_evidence" jsonb DEFAULT '{}'::jsonb NOT NULL, - "error" text, + "kind" text NOT NULL, + "title" text NOT NULL, + "content" text NOT NULL, + "content_hash" text NOT NULL, + "status" text DEFAULT 'proposed' NOT NULL, + "classification" text DEFAULT 'internal' NOT NULL, + "evidence_references" jsonb DEFAULT '[]'::jsonb NOT NULL, + "source_installation_id" uuid, + "proposed_by_actor_id" uuid NOT NULL, + "supersedes_id" uuid, + "expires_at" timestamp with time zone, + "reviewed_by_actor_id" uuid, + "reviewed_at" timestamp with time zone, + "review_reason" text, + "policy_decision" text DEFAULT 'pending_review' NOT NULL, "created_at" timestamp with time zone DEFAULT now() NOT NULL, - "updated_at" timestamp with time zone DEFAULT now() NOT NULL + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "operational_knowledge_kind_check" CHECK ("operational_knowledge"."kind" in ('fact','finding','correction','procedure')), + CONSTRAINT "operational_knowledge_status_check" CHECK ("operational_knowledge"."status" in ('proposed','accepted','quarantined','rejected','superseded','expired')), + CONSTRAINT "operational_knowledge_policy_check" CHECK ("operational_knowledge"."policy_decision" in ('accepted','quarantined','rejected','pending_review')), + CONSTRAINT "operational_knowledge_classification_check" CHECK ("operational_knowledge"."classification" in ('public','internal','confidential','restricted')) ); --> statement-breakpoint -ALTER TABLE "governed_missions" ADD CONSTRAINT "governed_missions_organisation_id_organisations_id_fk" FOREIGN KEY ("organisation_id") REFERENCES "public"."organisations"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "governed_missions" ADD CONSTRAINT "governed_missions_created_by_actor_id_actors_id_fk" FOREIGN KEY ("created_by_actor_id") REFERENCES "public"."actors"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "governed_missions" ADD CONSTRAINT "governed_missions_updated_by_actor_id_actors_id_fk" FOREIGN KEY ("updated_by_actor_id") REFERENCES "public"."actors"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "governed_missions" ADD CONSTRAINT "governed_missions_creator_org_fk" FOREIGN KEY ("created_by_actor_id","organisation_id") REFERENCES "public"."actors"("id","organisation_id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint ALTER TABLE "governed_mission_runs" ADD CONSTRAINT "governed_mission_runs_organisation_id_organisations_id_fk" FOREIGN KEY ("organisation_id") REFERENCES "public"."organisations"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint ALTER TABLE "governed_mission_runs" ADD CONSTRAINT "governed_mission_runs_mission_id_governed_missions_id_fk" FOREIGN KEY ("mission_id") REFERENCES "public"."governed_missions"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint ALTER TABLE "governed_mission_runs" ADD CONSTRAINT "governed_mission_runs_hermes_installation_id_mcp_installations_id_fk" FOREIGN KEY ("hermes_installation_id") REFERENCES "public"."mcp_installations"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint ALTER TABLE "governed_mission_runs" ADD CONSTRAINT "governed_mission_runs_initiating_actor_id_actors_id_fk" FOREIGN KEY ("initiating_actor_id") REFERENCES "public"."actors"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint -CREATE INDEX "governed_missions_org_status_idx" ON "governed_missions" USING btree ("organisation_id","status");--> statement-breakpoint -CREATE UNIQUE INDEX "governed_missions_org_name_unique" ON "governed_missions" USING btree ("organisation_id","name");--> statement-breakpoint +ALTER TABLE "governed_missions" ADD CONSTRAINT "governed_missions_organisation_id_organisations_id_fk" FOREIGN KEY ("organisation_id") REFERENCES "public"."organisations"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "governed_missions" ADD CONSTRAINT "governed_missions_created_by_actor_id_actors_id_fk" FOREIGN KEY ("created_by_actor_id") REFERENCES "public"."actors"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "governed_missions" ADD CONSTRAINT "governed_missions_updated_by_actor_id_actors_id_fk" FOREIGN KEY ("updated_by_actor_id") REFERENCES "public"."actors"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "governed_missions" ADD CONSTRAINT "governed_missions_creator_org_fk" FOREIGN KEY ("created_by_actor_id","organisation_id") REFERENCES "public"."actors"("id","organisation_id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "operational_knowledge" ADD CONSTRAINT "operational_knowledge_organisation_id_organisations_id_fk" FOREIGN KEY ("organisation_id") REFERENCES "public"."organisations"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "operational_knowledge" ADD CONSTRAINT "operational_knowledge_source_installation_id_mcp_installations_id_fk" FOREIGN KEY ("source_installation_id") REFERENCES "public"."mcp_installations"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "operational_knowledge" ADD CONSTRAINT "operational_knowledge_proposed_by_actor_id_actors_id_fk" FOREIGN KEY ("proposed_by_actor_id") REFERENCES "public"."actors"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "operational_knowledge" ADD CONSTRAINT "operational_knowledge_reviewed_by_actor_id_actors_id_fk" FOREIGN KEY ("reviewed_by_actor_id") REFERENCES "public"."actors"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "operational_knowledge" ADD CONSTRAINT "operational_knowledge_proposer_org_fk" FOREIGN KEY ("proposed_by_actor_id","organisation_id") REFERENCES "public"."actors"("id","organisation_id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint CREATE UNIQUE INDEX "governed_mission_runs_org_idempotency_unique" ON "governed_mission_runs" USING btree ("organisation_id","idempotency_key");--> statement-breakpoint CREATE INDEX "governed_mission_runs_mission_idx" ON "governed_mission_runs" USING btree ("organisation_id","mission_id","created_at");--> statement-breakpoint -ALTER TABLE "governed_missions" ADD CONSTRAINT "governed_missions_status_check" CHECK ("status" in ('active','paused','cancelled','archived'));--> statement-breakpoint -ALTER TABLE "governed_mission_runs" ADD CONSTRAINT "governed_mission_runs_status_check" CHECK ("status" in ('accepted','blocked','running','succeeded','failed','cancelled')); +CREATE INDEX "governed_missions_org_status_idx" ON "governed_missions" USING btree ("organisation_id","status");--> statement-breakpoint +CREATE UNIQUE INDEX "governed_missions_org_name_unique" ON "governed_missions" USING btree ("organisation_id","name");--> statement-breakpoint +CREATE INDEX "operational_knowledge_org_status_idx" ON "operational_knowledge" USING btree ("organisation_id","status","created_at");--> statement-breakpoint +CREATE INDEX "operational_knowledge_org_hash_idx" ON "operational_knowledge" USING btree ("organisation_id","content_hash"); \ No newline at end of file diff --git a/packages/database/migrations/0022_operational_knowledge.sql b/packages/database/migrations/0022_operational_knowledge.sql deleted file mode 100644 index b9c7c2f..0000000 --- a/packages/database/migrations/0022_operational_knowledge.sql +++ /dev/null @@ -1,33 +0,0 @@ -CREATE TABLE "operational_knowledge" ( - "id" uuid PRIMARY KEY NOT NULL, - "organisation_id" uuid NOT NULL, - "kind" text NOT NULL, - "title" text NOT NULL, - "content" text NOT NULL, - "content_hash" text NOT NULL, - "status" text DEFAULT 'proposed' NOT NULL, - "classification" text DEFAULT 'internal' NOT NULL, - "evidence_references" jsonb DEFAULT '[]'::jsonb NOT NULL, - "source_installation_id" uuid, - "proposed_by_actor_id" uuid NOT NULL, - "supersedes_id" uuid, - "expires_at" timestamp with time zone, - "reviewed_by_actor_id" uuid, - "reviewed_at" timestamp with time zone, - "review_reason" text, - "policy_decision" text DEFAULT 'pending_review' NOT NULL, - "created_at" timestamp with time zone DEFAULT now() NOT NULL, - "updated_at" timestamp with time zone DEFAULT now() NOT NULL -); ---> statement-breakpoint -ALTER TABLE "operational_knowledge" ADD CONSTRAINT "operational_knowledge_organisation_id_organisations_id_fk" FOREIGN KEY ("organisation_id") REFERENCES "public"."organisations"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "operational_knowledge" ADD CONSTRAINT "operational_knowledge_source_installation_id_mcp_installations_id_fk" FOREIGN KEY ("source_installation_id") REFERENCES "public"."mcp_installations"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "operational_knowledge" ADD CONSTRAINT "operational_knowledge_proposed_by_actor_id_actors_id_fk" FOREIGN KEY ("proposed_by_actor_id") REFERENCES "public"."actors"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "operational_knowledge" ADD CONSTRAINT "operational_knowledge_reviewed_by_actor_id_actors_id_fk" FOREIGN KEY ("reviewed_by_actor_id") REFERENCES "public"."actors"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "operational_knowledge" ADD CONSTRAINT "operational_knowledge_proposer_org_fk" FOREIGN KEY ("proposed_by_actor_id","organisation_id") REFERENCES "public"."actors"("id","organisation_id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint -CREATE INDEX "operational_knowledge_org_status_idx" ON "operational_knowledge" USING btree ("organisation_id","status","created_at");--> statement-breakpoint -CREATE INDEX "operational_knowledge_org_hash_idx" ON "operational_knowledge" USING btree ("organisation_id","content_hash");--> statement-breakpoint -ALTER TABLE "operational_knowledge" ADD CONSTRAINT "operational_knowledge_kind_check" CHECK ("kind" in ('fact','finding','correction','procedure'));--> statement-breakpoint -ALTER TABLE "operational_knowledge" ADD CONSTRAINT "operational_knowledge_status_check" CHECK ("status" in ('proposed','accepted','quarantined','rejected','superseded','expired'));--> statement-breakpoint -ALTER TABLE "operational_knowledge" ADD CONSTRAINT "operational_knowledge_policy_check" CHECK ("policy_decision" in ('accepted','quarantined','rejected','pending_review'));--> statement-breakpoint -ALTER TABLE "operational_knowledge" ADD CONSTRAINT "operational_knowledge_classification_check" CHECK ("classification" in ('public','internal','confidential','restricted')); diff --git a/packages/database/migrations/meta/0022_snapshot.json b/packages/database/migrations/meta/0022_snapshot.json new file mode 100644 index 0000000..24430bd --- /dev/null +++ b/packages/database/migrations/meta/0022_snapshot.json @@ -0,0 +1,13460 @@ +{ + "id": "3d8108bf-48a7-4180-8026-097867bb21bc", + "prevId": "57e02959-32dd-44df-8620-ea69bd36ee22", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.actors": { + "name": "actors", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "actor_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "avatar": { + "name": "avatar", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "identity_reference": { + "name": "identity_reference", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "capability_assignments": { + "name": "capability_assignments", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "actors_org_type_idx": { + "name": "actors_org_type_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "actor_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "actors_org_identity_unique": { + "name": "actors_org_identity_unique", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "identity_reference", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"actors\".\"identity_reference\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "actors_id_organisation_unique": { + "name": "actors_id_organisation_unique", + "columns": [ + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "actors_organisation_id_organisations_id_fk": { + "name": "actors_organisation_id_organisations_id_fk", + "tableFrom": "actors", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_definitions": { + "name": "agent_definitions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "avatar": { + "name": "avatar", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "runtime": { + "name": "runtime", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_actor_id": { + "name": "owner_actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "system_prompt_version": { + "name": "system_prompt_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "allowed_tools": { + "name": "allowed_tools", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "allowed_rooms": { + "name": "allowed_rooms", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "capability_requirements": { + "name": "capability_requirements", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "maximum_runtime_seconds": { + "name": "maximum_runtime_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 300 + }, + "maximum_token_budget": { + "name": "maximum_token_budget", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 20000 + }, + "maximum_cost_cents": { + "name": "maximum_cost_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 500 + }, + "data_classification_allowance": { + "name": "data_classification_allowance", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[\"internal\"]'::jsonb" + }, + "approval_requirements": { + "name": "approval_requirements", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "requested_permission_mode": { + "name": "requested_permission_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'read_only'" + }, + "kill_switch": { + "name": "kill_switch", + "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": { + "agent_definitions_org_name_unique": { + "name": "agent_definitions_org_name_unique", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_definitions_organisation_id_organisations_id_fk": { + "name": "agent_definitions_organisation_id_organisations_id_fk", + "tableFrom": "agent_definitions", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_definitions_owner_actor_id_actors_id_fk": { + "name": "agent_definitions_owner_actor_id_actors_id_fk", + "tableFrom": "agent_definitions", + "tableTo": "actors", + "columnsFrom": [ + "owner_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "agent_definitions_requested_permission_check": { + "name": "agent_definitions_requested_permission_check", + "value": "\"agent_definitions\".\"requested_permission_mode\" in ('read_only','approval_gated')" + } + }, + "isRLSEnabled": false + }, + "public.agent_memories": { + "name": "agent_memories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_run_id": { + "name": "source_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "evidence_references": { + "name": "evidence_references", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "confidence": { + "name": "confidence", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "classification": { + "name": "classification", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'internal'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "supersedes_memory_id": { + "name": "supersedes_memory_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "reviewed_by_actor_id": { + "name": "reviewed_by_actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "reviewed_at": { + "name": "reviewed_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": { + "agent_memories_org_agent_idx": { + "name": "agent_memories_org_agent_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_memories_organisation_id_organisations_id_fk": { + "name": "agent_memories_organisation_id_organisations_id_fk", + "tableFrom": "agent_memories", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_memories_agent_id_agent_definitions_id_fk": { + "name": "agent_memories_agent_id_agent_definitions_id_fk", + "tableFrom": "agent_memories", + "tableTo": "agent_definitions", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_memories_source_run_id_agent_runs_id_fk": { + "name": "agent_memories_source_run_id_agent_runs_id_fk", + "tableFrom": "agent_memories", + "tableTo": "agent_runs", + "columnsFrom": [ + "source_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_memories_reviewed_by_actor_id_actors_id_fk": { + "name": "agent_memories_reviewed_by_actor_id_actors_id_fk", + "tableFrom": "agent_memories", + "tableTo": "actors", + "columnsFrom": [ + "reviewed_by_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "agent_memories_kind_check": { + "name": "agent_memories_kind_check", + "value": "\"agent_memories\".\"kind\" in ('fact','preference','lesson','failure','procedure_hint')" + }, + "agent_memories_status_check": { + "name": "agent_memories_status_check", + "value": "\"agent_memories\".\"status\" in ('active','superseded','expired','rejected')" + }, + "agent_memories_confidence_check": { + "name": "agent_memories_confidence_check", + "value": "\"agent_memories\".\"confidence\" between 0 and 100" + } + }, + "isRLSEnabled": false + }, + "public.agent_readiness_snapshots": { + "name": "agent_readiness_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "process_identity": { + "name": "process_identity", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "gateway_state": { + "name": "gateway_state", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "authentication_state": { + "name": "authentication_state", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "observer_state": { + "name": "observer_state", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lifecycle_evidence_state": { + "name": "lifecycle_evidence_state", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lifecycle_state": { + "name": "lifecycle_state", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "capability_state": { + "name": "capability_state", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_state": { + "name": "tool_state", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission_state": { + "name": "permission_state", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reported_runtime": { + "name": "reported_runtime", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reported_provider": { + "name": "reported_provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reported_model": { + "name": "reported_model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "input_capabilities": { + "name": "input_capabilities", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "output_capabilities": { + "name": "output_capabilities", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "available_commands": { + "name": "available_commands", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "tool_sources": { + "name": "tool_sources", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "tool_risk_classes": { + "name": "tool_risk_classes", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "requested_permission_mode": { + "name": "requested_permission_mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "effective_permission_mode": { + "name": "effective_permission_mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "limitations": { + "name": "limitations", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "heartbeat_at": { + "name": "heartbeat_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "verified_at": { + "name": "verified_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": { + "agent_readiness_org_agent_verified_idx": { + "name": "agent_readiness_org_agent_verified_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "verified_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_readiness_org_process_verified_idx": { + "name": "agent_readiness_org_process_verified_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "process_identity", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "verified_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_readiness_snapshots_organisation_id_organisations_id_fk": { + "name": "agent_readiness_snapshots_organisation_id_organisations_id_fk", + "tableFrom": "agent_readiness_snapshots", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_readiness_snapshots_agent_id_agent_definitions_id_fk": { + "name": "agent_readiness_snapshots_agent_id_agent_definitions_id_fk", + "tableFrom": "agent_readiness_snapshots", + "tableTo": "agent_definitions", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "agent_readiness_evidence_states_check": { + "name": "agent_readiness_evidence_states_check", + "value": "\"agent_readiness_snapshots\".\"gateway_state\" in ('reported','unavailable','unknown')\n and \"agent_readiness_snapshots\".\"authentication_state\" in ('reported','unavailable','unknown')\n and \"agent_readiness_snapshots\".\"observer_state\" in ('reported','unavailable','unknown')\n and \"agent_readiness_snapshots\".\"lifecycle_evidence_state\" in ('reported','unavailable','unknown')\n and \"agent_readiness_snapshots\".\"capability_state\" in ('reported','unavailable','unknown')\n and \"agent_readiness_snapshots\".\"tool_state\" in ('reported','unavailable','unknown')\n and \"agent_readiness_snapshots\".\"permission_state\" in ('reported','unavailable','unknown')" + }, + "agent_readiness_lifecycle_state_check": { + "name": "agent_readiness_lifecycle_state_check", + "value": "\"agent_readiness_snapshots\".\"lifecycle_state\" in ('idle','running','stopped','failed','unknown')" + }, + "agent_readiness_permission_modes_check": { + "name": "agent_readiness_permission_modes_check", + "value": "\"agent_readiness_snapshots\".\"requested_permission_mode\" in ('read_only','approval_gated','unknown')\n and \"agent_readiness_snapshots\".\"effective_permission_mode\" in ('read_only','approval_gated','unknown')" + } + }, + "isRLSEnabled": false + }, + "public.agent_run_events": { + "name": "agent_run_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_run_events_org_run_idx": { + "name": "agent_run_events_org_run_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_run_events_organisation_id_organisations_id_fk": { + "name": "agent_run_events_organisation_id_organisations_id_fk", + "tableFrom": "agent_run_events", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_run_events_run_id_agent_runs_id_fk": { + "name": "agent_run_events_run_id_agent_runs_id_fk", + "tableFrom": "agent_run_events", + "tableTo": "agent_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_run_sources": { + "name": "agent_run_sources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "classification": { + "name": "classification", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'internal'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_run_sources_org_run_unique": { + "name": "agent_run_sources_org_run_unique", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_run_sources_org_run_idx": { + "name": "agent_run_sources_org_run_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_run_sources_organisation_id_organisations_id_fk": { + "name": "agent_run_sources_organisation_id_organisations_id_fk", + "tableFrom": "agent_run_sources", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_run_sources_run_id_agent_runs_id_fk": { + "name": "agent_run_sources_run_id_agent_runs_id_fk", + "tableFrom": "agent_run_sources", + "tableTo": "agent_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_runs": { + "name": "agent_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "room_id": { + "name": "room_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "investigation_id": { + "name": "investigation_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "workflow_run_id": { + "name": "workflow_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_by_actor_id": { + "name": "requested_by_actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "request": { + "name": "request", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "progress": { + "name": "progress", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "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 + }, + "heartbeat_at": { + "name": "heartbeat_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "lease_expires_at": { + "name": "lease_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "deadline_at": { + "name": "deadline_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cancellation_requested_at": { + "name": "cancellation_requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "worker_id": { + "name": "worker_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "input_hash": { + "name": "input_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prompt_hash": { + "name": "prompt_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "output_hash": { + "name": "output_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "output_schema": { + "name": "output_schema", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prompt_version": { + "name": "prompt_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "runtime": { + "name": "runtime", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "maximum_runtime_seconds": { + "name": "maximum_runtime_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 300 + }, + "maximum_token_budget": { + "name": "maximum_token_budget", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 20000 + }, + "maximum_cost_cents": { + "name": "maximum_cost_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 500 + }, + "token_usage": { + "name": "token_usage", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "estimated_cost_cents": { + "name": "estimated_cost_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "tool_call_count": { + "name": "tool_call_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "diagnostics": { + "name": "diagnostics", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "failure_code": { + "name": "failure_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cancellation_reason": { + "name": "cancellation_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "structured_output": { + "name": "structured_output", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "agent_runs_org_idempotency_unique": { + "name": "agent_runs_org_idempotency_unique", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_runs_org_status_idx": { + "name": "agent_runs_org_status_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_runs_recovery_idx": { + "name": "agent_runs_recovery_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lease_expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_runs_agent_id_agent_definitions_id_fk": { + "name": "agent_runs_agent_id_agent_definitions_id_fk", + "tableFrom": "agent_runs", + "tableTo": "agent_definitions", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_runs_organisation_id_organisations_id_fk": { + "name": "agent_runs_organisation_id_organisations_id_fk", + "tableFrom": "agent_runs", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_runs_room_id_rooms_id_fk": { + "name": "agent_runs_room_id_rooms_id_fk", + "tableFrom": "agent_runs", + "tableTo": "rooms", + "columnsFrom": [ + "room_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_runs_investigation_id_investigations_id_fk": { + "name": "agent_runs_investigation_id_investigations_id_fk", + "tableFrom": "agent_runs", + "tableTo": "investigations", + "columnsFrom": [ + "investigation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_runs_requested_by_actor_id_actors_id_fk": { + "name": "agent_runs_requested_by_actor_id_actors_id_fk", + "tableFrom": "agent_runs", + "tableTo": "actors", + "columnsFrom": [ + "requested_by_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_skill_evaluations": { + "name": "agent_skill_evaluations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "skill_version_id": { + "name": "skill_version_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "evaluator_actor_id": { + "name": "evaluator_actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "suite": { + "name": "suite", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "passed": { + "name": "passed", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "score": { + "name": "score", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "baseline_score": { + "name": "baseline_score", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "regressions": { + "name": "regressions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_skill_evaluations_version_idx": { + "name": "agent_skill_evaluations_version_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "skill_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_skill_evaluations_organisation_id_organisations_id_fk": { + "name": "agent_skill_evaluations_organisation_id_organisations_id_fk", + "tableFrom": "agent_skill_evaluations", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_skill_evaluations_skill_version_id_agent_skill_versions_id_fk": { + "name": "agent_skill_evaluations_skill_version_id_agent_skill_versions_id_fk", + "tableFrom": "agent_skill_evaluations", + "tableTo": "agent_skill_versions", + "columnsFrom": [ + "skill_version_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_skill_evaluations_evaluator_actor_id_actors_id_fk": { + "name": "agent_skill_evaluations_evaluator_actor_id_actors_id_fk", + "tableFrom": "agent_skill_evaluations", + "tableTo": "actors", + "columnsFrom": [ + "evaluator_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "agent_skill_evaluations_score_check": { + "name": "agent_skill_evaluations_score_check", + "value": "\"agent_skill_evaluations\".\"score\" between 0 and 100" + } + }, + "isRLSEnabled": false + }, + "public.agent_skill_versions": { + "name": "agent_skill_versions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "skill_id": { + "name": "skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "source_run_id": { + "name": "source_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "based_on_version_id": { + "name": "based_on_version_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "change_rationale": { + "name": "change_rationale", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "evidence_references": { + "name": "evidence_references", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "required_capabilities": { + "name": "required_capabilities", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "allowed_tools": { + "name": "allowed_tools", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'proposed'" + }, + "approved_by_actor_id": { + "name": "approved_by_actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approved_at": { + "name": "approved_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": { + "agent_skill_versions_skill_version_unique": { + "name": "agent_skill_versions_skill_version_unique", + "columns": [ + { + "expression": "skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_skill_versions_content_hash_unique": { + "name": "agent_skill_versions_content_hash_unique", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "content_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_skill_versions_organisation_id_organisations_id_fk": { + "name": "agent_skill_versions_organisation_id_organisations_id_fk", + "tableFrom": "agent_skill_versions", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_skill_versions_skill_id_agent_skills_id_fk": { + "name": "agent_skill_versions_skill_id_agent_skills_id_fk", + "tableFrom": "agent_skill_versions", + "tableTo": "agent_skills", + "columnsFrom": [ + "skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_skill_versions_source_run_id_agent_runs_id_fk": { + "name": "agent_skill_versions_source_run_id_agent_runs_id_fk", + "tableFrom": "agent_skill_versions", + "tableTo": "agent_runs", + "columnsFrom": [ + "source_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_skill_versions_approved_by_actor_id_actors_id_fk": { + "name": "agent_skill_versions_approved_by_actor_id_actors_id_fk", + "tableFrom": "agent_skill_versions", + "tableTo": "actors", + "columnsFrom": [ + "approved_by_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "agent_skill_versions_state_check": { + "name": "agent_skill_versions_state_check", + "value": "\"agent_skill_versions\".\"state\" in ('proposed','evaluating','approved','rejected','published','rolled_back')" + } + }, + "isRLSEnabled": false + }, + "public.agent_skills": { + "name": "agent_skills", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "skill_key": { + "name": "skill_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "active_version_id": { + "name": "active_version_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_actor_id": { + "name": "created_by_actor_id", + "type": "uuid", + "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": { + "agent_skills_org_agent_key_unique": { + "name": "agent_skills_org_agent_key_unique", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "skill_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_skills_organisation_id_organisations_id_fk": { + "name": "agent_skills_organisation_id_organisations_id_fk", + "tableFrom": "agent_skills", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_skills_agent_id_agent_definitions_id_fk": { + "name": "agent_skills_agent_id_agent_definitions_id_fk", + "tableFrom": "agent_skills", + "tableTo": "agent_definitions", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_skills_created_by_actor_id_actors_id_fk": { + "name": "agent_skills_created_by_actor_id_actors_id_fk", + "tableFrom": "agent_skills", + "tableTo": "actors", + "columnsFrom": [ + "created_by_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "agent_skills_status_check": { + "name": "agent_skills_status_check", + "value": "\"agent_skills\".\"status\" in ('draft','evaluating','published','retired')" + } + }, + "isRLSEnabled": false + }, + "public.agent_tool_calls": { + "name": "agent_tool_calls", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "capability": { + "name": "capability", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "classification": { + "name": "classification", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "arguments_hash": { + "name": "arguments_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "result_hash": { + "name": "result_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_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 + } + }, + "indexes": { + "agent_tool_calls_org_run_idx": { + "name": "agent_tool_calls_org_run_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_tool_calls_organisation_id_organisations_id_fk": { + "name": "agent_tool_calls_organisation_id_organisations_id_fk", + "tableFrom": "agent_tool_calls", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_tool_calls_run_id_agent_runs_id_fk": { + "name": "agent_tool_calls_run_id_agent_runs_id_fk", + "tableFrom": "agent_tool_calls", + "tableTo": "agent_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_tool_calls_approval_id_approvals_id_fk": { + "name": "agent_tool_calls_approval_id_approvals_id_fk", + "tableFrom": "agent_tool_calls", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.alerts": { + "name": "alerts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_product": { + "name": "source_product", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_instance": { + "name": "source_instance", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_reference": { + "name": "external_reference", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "severity": { + "name": "severity", + "type": "severity", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "alert_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'new'" + }, + "rule_name": { + "name": "rule_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rule_id": { + "name": "rule_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "received_at": { + "name": "received_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "assigned_actor_id": { + "name": "assigned_actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "entities": { + "name": "entities", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "observables": { + "name": "observables", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "raw_reference_metadata": { + "name": "raw_reference_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "investigation_id": { + "name": "investigation_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "kelpie_case_id": { + "name": "kelpie_case_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "room_id": { + "name": "room_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "dedupe_key": { + "name": "dedupe_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "correlation_key": { + "name": "correlation_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + } + }, + "indexes": { + "alerts_org_source_ref_unique": { + "name": "alerts_org_source_ref_unique", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_product", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_instance", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_reference", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "alerts_org_dedupe_unique": { + "name": "alerts_org_dedupe_unique", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "dedupe_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "alerts_org_queue_idx": { + "name": "alerts_org_queue_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "severity", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "received_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "alerts_search_idx": { + "name": "alerts_search_idx", + "columns": [ + { + "expression": "to_tsvector('english', \"title\" || ' ' || \"description\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "alerts_organisation_id_organisations_id_fk": { + "name": "alerts_organisation_id_organisations_id_fk", + "tableFrom": "alerts", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "alerts_assigned_actor_id_actors_id_fk": { + "name": "alerts_assigned_actor_id_actors_id_fk", + "tableFrom": "alerts", + "tableTo": "actors", + "columnsFrom": [ + "assigned_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "alerts_room_id_rooms_id_fk": { + "name": "alerts_room_id_rooms_id_fk", + "tableFrom": "alerts", + "tableTo": "rooms", + "columnsFrom": [ + "room_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.approvals": { + "name": "approvals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "requesting_actor_id": { + "name": "requesting_actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "action_type": { + "name": "action_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target": { + "name": "target", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "risk_summary": { + "name": "risk_summary", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requested_at": { + "name": "requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "required_capability": { + "name": "required_capability", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "required_approval_count": { + "name": "required_approval_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "status": { + "name": "status", + "type": "approval_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "decisions": { + "name": "decisions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "decision_at": { + "name": "decision_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "executed_at": { + "name": "executed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "approvals_org_idempotency_unique": { + "name": "approvals_org_idempotency_unique", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "approvals_org_status_idx": { + "name": "approvals_org_status_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "approvals_organisation_id_organisations_id_fk": { + "name": "approvals_organisation_id_organisations_id_fk", + "tableFrom": "approvals", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approvals_requesting_actor_id_actors_id_fk": { + "name": "approvals_requesting_actor_id_actors_id_fk", + "tableFrom": "approvals", + "tableTo": "actors", + "columnsFrom": [ + "requesting_actor_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": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sequence": { + "name": "sequence", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "actor_id": { + "name": "actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "actor_type", + "typeSchema": "public", + "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": true + }, + "previous_hash": { + "name": "previous_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_hash": { + "name": "event_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trace_id": { + "name": "trace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "audit_org_sequence_unique": { + "name": "audit_org_sequence_unique", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sequence", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_org_hash_unique": { + "name": "audit_org_hash_unique", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "event_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_org_target_idx": { + "name": "audit_org_target_idx", + "columns": [ + { + "expression": "organisation_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": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "audit_events_organisation_id_organisations_id_fk": { + "name": "audit_events_organisation_id_organisations_id_fk", + "tableFrom": "audit_events", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "audit_events_actor_id_actors_id_fk": { + "name": "audit_events_actor_id_actors_id_fk", + "tableFrom": "audit_events", + "tableTo": "actors", + "columnsFrom": [ + "actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_account": { + "name": "auth_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": { + "auth_accounts_user_idx": { + "name": "auth_accounts_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "auth_accounts_provider_unique": { + "name": "auth_accounts_provider_unique", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "auth_account_user_id_auth_user_id_fk": { + "name": "auth_account_user_id_auth_user_id_fk", + "tableFrom": "auth_account", + "tableTo": "auth_user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_passkey": { + "name": "auth_passkey", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "counter": { + "name": "counter", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "device_type": { + "name": "device_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "backed_up": { + "name": "backed_up", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "transports": { + "name": "transports", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "aaguid": { + "name": "aaguid", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "auth_passkey_user_idx": { + "name": "auth_passkey_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "auth_passkey_user_id_auth_user_id_fk": { + "name": "auth_passkey_user_id_auth_user_id_fk", + "tableFrom": "auth_passkey", + "tableTo": "auth_user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "auth_passkey_credential_id_unique": { + "name": "auth_passkey_credential_id_unique", + "nullsNotDistinct": false, + "columns": [ + "credential_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_session": { + "name": "auth_session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "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": { + "auth_sessions_user_idx": { + "name": "auth_sessions_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "auth_session_user_id_auth_user_id_fk": { + "name": "auth_session_user_id_auth_user_id_fk", + "tableFrom": "auth_session", + "tableTo": "auth_user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "auth_session_token_unique": { + "name": "auth_session_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_two_factor": { + "name": "auth_two_factor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "backup_codes": { + "name": "backup_codes", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "verified": { + "name": "verified", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "failed_verification_count": { + "name": "failed_verification_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "locked_until": { + "name": "locked_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "auth_two_factor_user_unique": { + "name": "auth_two_factor_user_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "auth_two_factor_user_id_auth_user_id_fk": { + "name": "auth_two_factor_user_id_auth_user_id_fk", + "tableFrom": "auth_two_factor", + "tableTo": "auth_user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_user": { + "name": "auth_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 + }, + "two_factor_enabled": { + "name": "two_factor_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "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": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "auth_user_email_unique": { + "name": "auth_user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_verification": { + "name": "auth_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": { + "auth_verification_identifier_idx": { + "name": "auth_verification_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.decisions": { + "name": "decisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "decision": { + "name": "decision", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rationale": { + "name": "rationale", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "decision_maker_actor_id": { + "name": "decision_maker_actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "alternatives_considered": { + "name": "alternatives_considered", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "evidence_references": { + "name": "evidence_references", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "related_investigation_id": { + "name": "related_investigation_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "related_case_id": { + "name": "related_case_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "related_workflow_run_id": { + "name": "related_workflow_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decisions_org_investigation_idx": { + "name": "decisions_org_investigation_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "related_investigation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decisions_organisation_id_organisations_id_fk": { + "name": "decisions_organisation_id_organisations_id_fk", + "tableFrom": "decisions", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decisions_decision_maker_actor_id_actors_id_fk": { + "name": "decisions_decision_maker_actor_id_actors_id_fk", + "tableFrom": "decisions", + "tableTo": "actors", + "columnsFrom": [ + "decision_maker_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decisions_related_investigation_id_investigations_id_fk": { + "name": "decisions_related_investigation_id_investigations_id_fk", + "tableFrom": "decisions", + "tableTo": "investigations", + "columnsFrom": [ + "related_investigation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.evidence": { + "name": "evidence", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "file_name": { + "name": "file_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "sha256": { + "name": "sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "uploaded_by_actor_id": { + "name": "uploaded_by_actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "classification": { + "name": "classification", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "related_room_id": { + "name": "related_room_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "related_investigation_id": { + "name": "related_investigation_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "related_case_id": { + "name": "related_case_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "original_timestamp": { + "name": "original_timestamp", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "storage_key": { + "name": "storage_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scan_state": { + "name": "scan_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "retention_state": { + "name": "retention_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "legal_hold": { + "name": "legal_hold", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "object_lock_metadata": { + "name": "object_lock_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + } + }, + "indexes": { + "evidence_org_hash_unique": { + "name": "evidence_org_hash_unique", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "evidence_search_idx": { + "name": "evidence_search_idx", + "columns": [ + { + "expression": "to_tsvector('english', \"file_name\" || ' ' || \"source\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "evidence_organisation_id_organisations_id_fk": { + "name": "evidence_organisation_id_organisations_id_fk", + "tableFrom": "evidence", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "evidence_uploaded_by_actor_id_actors_id_fk": { + "name": "evidence_uploaded_by_actor_id_actors_id_fk", + "tableFrom": "evidence", + "tableTo": "actors", + "columnsFrom": [ + "uploaded_by_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "evidence_related_room_id_rooms_id_fk": { + "name": "evidence_related_room_id_rooms_id_fk", + "tableFrom": "evidence", + "tableTo": "rooms", + "columnsFrom": [ + "related_room_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "evidence_related_investigation_id_investigations_id_fk": { + "name": "evidence_related_investigation_id_investigations_id_fk", + "tableFrom": "evidence", + "tableTo": "investigations", + "columnsFrom": [ + "related_investigation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.findings": { + "name": "findings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "investigation_id": { + "name": "investigation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by_actor_id": { + "name": "created_by_actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "confidence": { + "name": "confidence", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "severity": { + "name": "severity", + "type": "severity", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "supporting_evidence": { + "name": "supporting_evidence", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "related_entities": { + "name": "related_entities", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "related_observables": { + "name": "related_observables", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "recommended_action": { + "name": "recommended_action", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_provenance": { + "name": "agent_provenance", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "human_reviewed_at": { + "name": "human_reviewed_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()" + }, + "superseded_at": { + "name": "superseded_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "findings_org_investigation_idx": { + "name": "findings_org_investigation_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "investigation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "findings_search_idx": { + "name": "findings_search_idx", + "columns": [ + { + "expression": "to_tsvector('english', \"title\" || ' ' || \"summary\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "findings_organisation_id_organisations_id_fk": { + "name": "findings_organisation_id_organisations_id_fk", + "tableFrom": "findings", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "findings_investigation_id_investigations_id_fk": { + "name": "findings_investigation_id_investigations_id_fk", + "tableFrom": "findings", + "tableTo": "investigations", + "columnsFrom": [ + "investigation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "findings_created_by_actor_id_actors_id_fk": { + "name": "findings_created_by_actor_id_actors_id_fk", + "tableFrom": "findings", + "tableTo": "actors", + "columnsFrom": [ + "created_by_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.governed_mission_runs": { + "name": "governed_mission_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "mission_id": { + "name": "mission_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'accepted'" + }, + "hermes_installation_id": { + "name": "hermes_installation_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "hermes_profile": { + "name": "hermes_profile", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "initiating_actor_id": { + "name": "initiating_actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "delivery_evidence": { + "name": "delivery_evidence", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "error": { + "name": "error", + "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": { + "governed_mission_runs_org_idempotency_unique": { + "name": "governed_mission_runs_org_idempotency_unique", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "governed_mission_runs_mission_idx": { + "name": "governed_mission_runs_mission_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "mission_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "governed_mission_runs_organisation_id_organisations_id_fk": { + "name": "governed_mission_runs_organisation_id_organisations_id_fk", + "tableFrom": "governed_mission_runs", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "governed_mission_runs_mission_id_governed_missions_id_fk": { + "name": "governed_mission_runs_mission_id_governed_missions_id_fk", + "tableFrom": "governed_mission_runs", + "tableTo": "governed_missions", + "columnsFrom": [ + "mission_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "governed_mission_runs_hermes_installation_id_mcp_installations_id_fk": { + "name": "governed_mission_runs_hermes_installation_id_mcp_installations_id_fk", + "tableFrom": "governed_mission_runs", + "tableTo": "mcp_installations", + "columnsFrom": [ + "hermes_installation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "governed_mission_runs_initiating_actor_id_actors_id_fk": { + "name": "governed_mission_runs_initiating_actor_id_actors_id_fk", + "tableFrom": "governed_mission_runs", + "tableTo": "actors", + "columnsFrom": [ + "initiating_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "governed_mission_runs_status_check": { + "name": "governed_mission_runs_status_check", + "value": "\"governed_mission_runs\".\"status\" in ('accepted','blocked','running','succeeded','failed','cancelled')" + } + }, + "isRLSEnabled": false + }, + "public.governed_missions": { + "name": "governed_missions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "capability_envelope": { + "name": "capability_envelope", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "schedule_hint": { + "name": "schedule_hint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cancellation_policy": { + "name": "cancellation_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"killSwitchBlocksNew\":true}'::jsonb" + }, + "hermes_profile": { + "name": "hermes_profile", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_actor_id": { + "name": "created_by_actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "updated_by_actor_id": { + "name": "updated_by_actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "kill_switch": { + "name": "kill_switch", + "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": { + "governed_missions_org_status_idx": { + "name": "governed_missions_org_status_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "governed_missions_org_name_unique": { + "name": "governed_missions_org_name_unique", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "governed_missions_organisation_id_organisations_id_fk": { + "name": "governed_missions_organisation_id_organisations_id_fk", + "tableFrom": "governed_missions", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "governed_missions_created_by_actor_id_actors_id_fk": { + "name": "governed_missions_created_by_actor_id_actors_id_fk", + "tableFrom": "governed_missions", + "tableTo": "actors", + "columnsFrom": [ + "created_by_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "governed_missions_updated_by_actor_id_actors_id_fk": { + "name": "governed_missions_updated_by_actor_id_actors_id_fk", + "tableFrom": "governed_missions", + "tableTo": "actors", + "columnsFrom": [ + "updated_by_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "governed_missions_creator_org_fk": { + "name": "governed_missions_creator_org_fk", + "tableFrom": "governed_missions", + "tableTo": "actors", + "columnsFrom": [ + "created_by_actor_id", + "organisation_id" + ], + "columnsTo": [ + "id", + "organisation_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "governed_missions_status_check": { + "name": "governed_missions_status_check", + "value": "\"governed_missions\".\"status\" in ('active','paused','cancelled','archived')" + } + }, + "isRLSEnabled": false + }, + "public.hunt_queries": { + "name": "hunt_queries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "hunt_id": { + "name": "hunt_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "integration_id": { + "name": "integration_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "template_id": { + "name": "template_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "query_run_id": { + "name": "query_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_key": { + "name": "source_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sequence": { + "name": "sequence", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "rationale": { + "name": "rationale", + "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": { + "hunt_queries_org_query_run_unique": { + "name": "hunt_queries_org_query_run_unique", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "query_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "hunt_queries_org_hunt_sequence_unique": { + "name": "hunt_queries_org_hunt_sequence_unique", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "hunt_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sequence", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "hunt_queries_org_hunt_idx": { + "name": "hunt_queries_org_hunt_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "hunt_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sequence", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "hunt_queries_organisation_id_organisations_id_fk": { + "name": "hunt_queries_organisation_id_organisations_id_fk", + "tableFrom": "hunt_queries", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "hunt_queries_hunt_id_hunt_runs_id_fk": { + "name": "hunt_queries_hunt_id_hunt_runs_id_fk", + "tableFrom": "hunt_queries", + "tableTo": "hunt_runs", + "columnsFrom": [ + "hunt_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "hunt_queries_integration_id_integration_records_id_fk": { + "name": "hunt_queries_integration_id_integration_records_id_fk", + "tableFrom": "hunt_queries", + "tableTo": "integration_records", + "columnsFrom": [ + "integration_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "hunt_queries_template_id_integration_query_templates_id_fk": { + "name": "hunt_queries_template_id_integration_query_templates_id_fk", + "tableFrom": "hunt_queries", + "tableTo": "integration_query_templates", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "hunt_queries_query_run_id_integration_query_runs_id_fk": { + "name": "hunt_queries_query_run_id_integration_query_runs_id_fk", + "tableFrom": "hunt_queries", + "tableTo": "integration_query_runs", + "columnsFrom": [ + "query_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.hunt_runs": { + "name": "hunt_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_run_id": { + "name": "agent_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_message_id": { + "name": "source_message_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "room_id": { + "name": "room_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "linked_case_id": { + "name": "linked_case_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_by_actor_id": { + "name": "requested_by_actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "question": { + "name": "question", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "training_mode": { + "name": "training_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "plan": { + "name": "plan", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'planned'" + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "failure_code": { + "name": "failure_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_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": { + "hunt_runs_org_idempotency_unique": { + "name": "hunt_runs_org_idempotency_unique", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "hunt_runs_org_agent_run_unique": { + "name": "hunt_runs_org_agent_run_unique", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "hunt_runs_org_status_idx": { + "name": "hunt_runs_org_status_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "hunt_runs_organisation_id_organisations_id_fk": { + "name": "hunt_runs_organisation_id_organisations_id_fk", + "tableFrom": "hunt_runs", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "hunt_runs_agent_run_id_agent_runs_id_fk": { + "name": "hunt_runs_agent_run_id_agent_runs_id_fk", + "tableFrom": "hunt_runs", + "tableTo": "agent_runs", + "columnsFrom": [ + "agent_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "hunt_runs_task_id_tasks_id_fk": { + "name": "hunt_runs_task_id_tasks_id_fk", + "tableFrom": "hunt_runs", + "tableTo": "tasks", + "columnsFrom": [ + "task_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "hunt_runs_source_message_id_messages_id_fk": { + "name": "hunt_runs_source_message_id_messages_id_fk", + "tableFrom": "hunt_runs", + "tableTo": "messages", + "columnsFrom": [ + "source_message_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "hunt_runs_room_id_rooms_id_fk": { + "name": "hunt_runs_room_id_rooms_id_fk", + "tableFrom": "hunt_runs", + "tableTo": "rooms", + "columnsFrom": [ + "room_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "hunt_runs_requested_by_actor_id_actors_id_fk": { + "name": "hunt_runs_requested_by_actor_id_actors_id_fk", + "tableFrom": "hunt_runs", + "tableTo": "actors", + "columnsFrom": [ + "requested_by_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "hunt_runs_approval_id_approvals_id_fk": { + "name": "hunt_runs_approval_id_approvals_id_fk", + "tableFrom": "hunt_runs", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "hunt_runs_status_check": { + "name": "hunt_runs_status_check", + "value": "\"hunt_runs\".\"status\" in ('planned','awaiting_approval','querying','analysing','completed','failed','cancelled')" + } + }, + "isRLSEnabled": false + }, + "public.hypotheses": { + "name": "hypotheses", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "investigation_id": { + "name": "investigation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "statement": { + "name": "statement", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unverified'" + }, + "confidence": { + "name": "confidence", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 50 + }, + "created_by_actor_id": { + "name": "created_by_actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "supporting_finding_ids": { + "name": "supporting_finding_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "contradicting_finding_ids": { + "name": "contradicting_finding_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_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 + } + }, + "indexes": { + "hypotheses_org_investigation_idx": { + "name": "hypotheses_org_investigation_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "investigation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "hypotheses_organisation_id_organisations_id_fk": { + "name": "hypotheses_organisation_id_organisations_id_fk", + "tableFrom": "hypotheses", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "hypotheses_investigation_id_investigations_id_fk": { + "name": "hypotheses_investigation_id_investigations_id_fk", + "tableFrom": "hypotheses", + "tableTo": "investigations", + "columnsFrom": [ + "investigation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "hypotheses_created_by_actor_id_actors_id_fk": { + "name": "hypotheses_created_by_actor_id_actors_id_fk", + "tableFrom": "hypotheses", + "tableTo": "actors", + "columnsFrom": [ + "created_by_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.idempotency_records": { + "name": "idempotency_records", + "schema": "", + "columns": { + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_hash": { + "name": "request_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "response_status": { + "name": "response_status", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "response_body": { + "name": "response_body", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idempotency_expiry_idx": { + "name": "idempotency_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "idempotency_records_organisation_id_organisations_id_fk": { + "name": "idempotency_records_organisation_id_organisations_id_fk", + "tableFrom": "idempotency_records", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "idempotency_records_organisation_id_scope_key_pk": { + "name": "idempotency_records_organisation_id_scope_key_pk", + "columns": [ + "organisation_id", + "scope", + "key" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.integration_connector_credentials": { + "name": "integration_connector_credentials", + "schema": "", + "columns": { + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "integration_id": { + "name": "integration_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "encrypted_credential": { + "name": "encrypted_credential", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "envelope_version": { + "name": "envelope_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'v1'" + }, + "rotation_version": { + "name": "rotation_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "rotated_by_actor_id": { + "name": "rotated_by_actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "rotated_at": { + "name": "rotated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "integration_credentials_org_idx": { + "name": "integration_credentials_org_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "integration_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "integration_connector_credentials_organisation_id_organisations_id_fk": { + "name": "integration_connector_credentials_organisation_id_organisations_id_fk", + "tableFrom": "integration_connector_credentials", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "integration_connector_credentials_integration_id_integration_records_id_fk": { + "name": "integration_connector_credentials_integration_id_integration_records_id_fk", + "tableFrom": "integration_connector_credentials", + "tableTo": "integration_records", + "columnsFrom": [ + "integration_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "integration_connector_credentials_rotated_by_actor_id_actors_id_fk": { + "name": "integration_connector_credentials_rotated_by_actor_id_actors_id_fk", + "tableFrom": "integration_connector_credentials", + "tableTo": "actors", + "columnsFrom": [ + "rotated_by_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.integration_deliveries": { + "name": "integration_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "integration_id": { + "name": "integration_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "operation": { + "name": "operation", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "request_metadata": { + "name": "request_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "response_metadata": { + "name": "response_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_attempt_at": { + "name": "next_attempt_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": { + "integration_delivery_org_idempotency_unique": { + "name": "integration_delivery_org_idempotency_unique", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "integration_deliveries_organisation_id_organisations_id_fk": { + "name": "integration_deliveries_organisation_id_organisations_id_fk", + "tableFrom": "integration_deliveries", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "integration_deliveries_integration_id_integration_records_id_fk": { + "name": "integration_deliveries_integration_id_integration_records_id_fk", + "tableFrom": "integration_deliveries", + "tableTo": "integration_records", + "columnsFrom": [ + "integration_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.integration_entities": { + "name": "integration_entities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "integration_id": { + "name": "integration_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "posture": { + "name": "posture", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "integration_entities_org_external_unique": { + "name": "integration_entities_org_external_unique", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "integration_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "integration_entities_organisation_id_organisations_id_fk": { + "name": "integration_entities_organisation_id_organisations_id_fk", + "tableFrom": "integration_entities", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "integration_entities_integration_id_integration_records_id_fk": { + "name": "integration_entities_integration_id_integration_records_id_fk", + "tableFrom": "integration_entities", + "tableTo": "integration_records", + "columnsFrom": [ + "integration_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.integration_query_runs": { + "name": "integration_query_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "integration_id": { + "name": "integration_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "template_id": { + "name": "template_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "requested_by_actor_id": { + "name": "requested_by_actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trace_id": { + "name": "trace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "input": { + "name": "input", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "request_metadata": { + "name": "request_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "response_metadata": { + "name": "response_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "integration_query_runs_org_idempotency_unique": { + "name": "integration_query_runs_org_idempotency_unique", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "integration_query_runs_org_status_idx": { + "name": "integration_query_runs_org_status_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "integration_query_runs_organisation_id_organisations_id_fk": { + "name": "integration_query_runs_organisation_id_organisations_id_fk", + "tableFrom": "integration_query_runs", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "integration_query_runs_integration_id_integration_records_id_fk": { + "name": "integration_query_runs_integration_id_integration_records_id_fk", + "tableFrom": "integration_query_runs", + "tableTo": "integration_records", + "columnsFrom": [ + "integration_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "integration_query_runs_template_id_integration_query_templates_id_fk": { + "name": "integration_query_runs_template_id_integration_query_templates_id_fk", + "tableFrom": "integration_query_runs", + "tableTo": "integration_query_templates", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "integration_query_runs_requested_by_actor_id_actors_id_fk": { + "name": "integration_query_runs_requested_by_actor_id_actors_id_fk", + "tableFrom": "integration_query_runs", + "tableTo": "actors", + "columnsFrom": [ + "requested_by_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.integration_query_templates": { + "name": "integration_query_templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "integration_id": { + "name": "integration_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "template_key": { + "name": "template_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "definition": { + "name": "definition", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_by_actor_id": { + "name": "created_by_actor_id", + "type": "uuid", + "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": { + "integration_templates_org_key_version_unique": { + "name": "integration_templates_org_key_version_unique", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "integration_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "template_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "integration_query_templates_organisation_id_organisations_id_fk": { + "name": "integration_query_templates_organisation_id_organisations_id_fk", + "tableFrom": "integration_query_templates", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "integration_query_templates_integration_id_integration_records_id_fk": { + "name": "integration_query_templates_integration_id_integration_records_id_fk", + "tableFrom": "integration_query_templates", + "tableTo": "integration_records", + "columnsFrom": [ + "integration_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "integration_query_templates_created_by_actor_id_actors_id_fk": { + "name": "integration_query_templates_created_by_actor_id_actors_id_fk", + "tableFrom": "integration_query_templates", + "tableTo": "actors", + "columnsFrom": [ + "created_by_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.integration_records": { + "name": "integration_records", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "product": { + "name": "product", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mock": { + "name": "mock", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "configuration": { + "name": "configuration", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "health": { + "name": "health", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "cursor": { + "name": "cursor", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_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": { + "integrations_org_product_instance_unique": { + "name": "integrations_org_product_instance_unique", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "product", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "integration_records_organisation_id_organisations_id_fk": { + "name": "integration_records_organisation_id_organisations_id_fk", + "tableFrom": "integration_records", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.investigations": { + "name": "investigations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "investigation_number": { + "name": "investigation_number", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "status": { + "name": "status", + "type": "investigation_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "severity": { + "name": "severity", + "type": "severity", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "lead_actor_id": { + "name": "lead_actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "room_id": { + "name": "room_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_activity_at": { + "name": "last_activity_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "closed_at": { + "name": "closed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "recommendation": { + "name": "recommendation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "disposition": { + "name": "disposition", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "promotion_decision": { + "name": "promotion_decision", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "linked_kelpie_case_id": { + "name": "linked_kelpie_case_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + } + }, + "indexes": { + "investigations_org_number_unique": { + "name": "investigations_org_number_unique", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "investigation_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "investigations_org_queue_idx": { + "name": "investigations_org_queue_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "severity", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_activity_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "investigations_search_idx": { + "name": "investigations_search_idx", + "columns": [ + { + "expression": "to_tsvector('english', \"title\" || ' ' || \"summary\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "investigations_organisation_id_organisations_id_fk": { + "name": "investigations_organisation_id_organisations_id_fk", + "tableFrom": "investigations", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "investigations_lead_actor_id_actors_id_fk": { + "name": "investigations_lead_actor_id_actors_id_fk", + "tableFrom": "investigations", + "tableTo": "actors", + "columnsFrom": [ + "lead_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "investigations_room_id_rooms_id_fk": { + "name": "investigations_room_id_rooms_id_fk", + "tableFrom": "investigations", + "tableTo": "rooms", + "columnsFrom": [ + "room_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_installations": { + "name": "mcp_installations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_prefix": { + "name": "token_prefix", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scopes": { + "name": "scopes", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "bound_actor_id": { + "name": "bound_actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "installed_by_actor_id": { + "name": "installed_by_actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "installed_at": { + "name": "installed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_by_actor_id": { + "name": "revoked_by_actor_id", + "type": "uuid", + "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": { + "mcp_installations_token_hash_unique": { + "name": "mcp_installations_token_hash_unique", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_installations_org_status_idx": { + "name": "mcp_installations_org_status_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_installations_organisation_id_organisations_id_fk": { + "name": "mcp_installations_organisation_id_organisations_id_fk", + "tableFrom": "mcp_installations", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "mcp_installations_bound_actor_org_fk": { + "name": "mcp_installations_bound_actor_org_fk", + "tableFrom": "mcp_installations", + "tableTo": "actors", + "columnsFrom": [ + "bound_actor_id", + "organisation_id" + ], + "columnsTo": [ + "id", + "organisation_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "mcp_installations_installed_by_actor_org_fk": { + "name": "mcp_installations_installed_by_actor_org_fk", + "tableFrom": "mcp_installations", + "tableTo": "actors", + "columnsFrom": [ + "installed_by_actor_id", + "organisation_id" + ], + "columnsTo": [ + "id", + "organisation_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "mcp_installations_revoked_by_actor_org_fk": { + "name": "mcp_installations_revoked_by_actor_org_fk", + "tableFrom": "mcp_installations", + "tableTo": "actors", + "columnsFrom": [ + "revoked_by_actor_id", + "organisation_id" + ], + "columnsTo": [ + "id", + "organisation_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.message_mentions": { + "name": "message_mentions", + "schema": "", + "columns": { + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "mentioned_actor_id": { + "name": "mentioned_actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "mention_type": { + "name": "mention_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mention_key": { + "name": "mention_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "message_mentions_org_actor_idx": { + "name": "message_mentions_org_actor_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "mentioned_actor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "message_mentions_organisation_id_organisations_id_fk": { + "name": "message_mentions_organisation_id_organisations_id_fk", + "tableFrom": "message_mentions", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "message_mentions_message_id_messages_id_fk": { + "name": "message_mentions_message_id_messages_id_fk", + "tableFrom": "message_mentions", + "tableTo": "messages", + "columnsFrom": [ + "message_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "message_mentions_mentioned_actor_id_actors_id_fk": { + "name": "message_mentions_mentioned_actor_id_actors_id_fk", + "tableFrom": "message_mentions", + "tableTo": "actors", + "columnsFrom": [ + "mentioned_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "message_mentions_message_id_mention_type_mention_key_pk": { + "name": "message_mentions_message_id_mention_type_mention_key_pk", + "columns": [ + "message_id", + "mention_type", + "mention_key" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "message_mention_type_check": { + "name": "message_mention_type_check", + "value": "\"message_mentions\".\"mention_type\" in ('actor','room','everyone')" + } + }, + "isRLSEnabled": false + }, + "public.message_pins": { + "name": "message_pins", + "schema": "", + "columns": { + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "room_id": { + "name": "room_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pinned_by_actor_id": { + "name": "pinned_by_actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "message_pins_org_room_idx": { + "name": "message_pins_org_room_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "room_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "message_pins_organisation_id_organisations_id_fk": { + "name": "message_pins_organisation_id_organisations_id_fk", + "tableFrom": "message_pins", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "message_pins_room_id_rooms_id_fk": { + "name": "message_pins_room_id_rooms_id_fk", + "tableFrom": "message_pins", + "tableTo": "rooms", + "columnsFrom": [ + "room_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "message_pins_message_id_messages_id_fk": { + "name": "message_pins_message_id_messages_id_fk", + "tableFrom": "message_pins", + "tableTo": "messages", + "columnsFrom": [ + "message_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "message_pins_pinned_by_actor_id_actors_id_fk": { + "name": "message_pins_pinned_by_actor_id_actors_id_fk", + "tableFrom": "message_pins", + "tableTo": "actors", + "columnsFrom": [ + "pinned_by_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "message_pins_room_id_message_id_pk": { + "name": "message_pins_room_id_message_id_pk", + "columns": [ + "room_id", + "message_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.message_revisions": { + "name": "message_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "actor_id": { + "name": "actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision_type": { + "name": "revision_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "previous_document": { + "name": "previous_document", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "previous_plain_text": { + "name": "previous_plain_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "next_document": { + "name": "next_document", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "next_plain_text": { + "name": "next_plain_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "message_revisions_org_message_idx": { + "name": "message_revisions_org_message_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "message_revisions_org_idempotency_unique": { + "name": "message_revisions_org_idempotency_unique", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "message_revisions_organisation_id_organisations_id_fk": { + "name": "message_revisions_organisation_id_organisations_id_fk", + "tableFrom": "message_revisions", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "message_revisions_message_id_messages_id_fk": { + "name": "message_revisions_message_id_messages_id_fk", + "tableFrom": "message_revisions", + "tableTo": "messages", + "columnsFrom": [ + "message_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "message_revisions_actor_id_actors_id_fk": { + "name": "message_revisions_actor_id_actors_id_fk", + "tableFrom": "message_revisions", + "tableTo": "actors", + "columnsFrom": [ + "actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "message_revision_type_check": { + "name": "message_revision_type_check", + "value": "\"message_revisions\".\"revision_type\" in ('edit','delete')" + } + }, + "isRLSEnabled": false + }, + "public.message_saves": { + "name": "message_saves", + "schema": "", + "columns": { + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "actor_id": { + "name": "actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "message_saves_org_actor_idx": { + "name": "message_saves_org_actor_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "actor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "message_saves_organisation_id_organisations_id_fk": { + "name": "message_saves_organisation_id_organisations_id_fk", + "tableFrom": "message_saves", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "message_saves_message_id_messages_id_fk": { + "name": "message_saves_message_id_messages_id_fk", + "tableFrom": "message_saves", + "tableTo": "messages", + "columnsFrom": [ + "message_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "message_saves_actor_id_actors_id_fk": { + "name": "message_saves_actor_id_actors_id_fk", + "tableFrom": "message_saves", + "tableTo": "actors", + "columnsFrom": [ + "actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "message_saves_message_id_actor_id_pk": { + "name": "message_saves_message_id_actor_id_pk", + "columns": [ + "message_id", + "actor_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.messages": { + "name": "messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "room_id": { + "name": "room_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "thread_parent_id": { + "name": "thread_parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_actor_id": { + "name": "author_actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "message_type": { + "name": "message_type", + "type": "message_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "document": { + "name": "document", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "plain_text": { + "name": "plain_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "edited_at": { + "name": "edited_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "data_classification": { + "name": "data_classification", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'internal'" + }, + "related_alert_id": { + "name": "related_alert_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "related_investigation_id": { + "name": "related_investigation_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "related_case_id": { + "name": "related_case_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "related_agent_run_id": { + "name": "related_agent_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "related_workflow_run_id": { + "name": "related_workflow_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "messages_org_room_time_idx": { + "name": "messages_org_room_time_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "room_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "messages_thread_idx": { + "name": "messages_thread_idx", + "columns": [ + { + "expression": "thread_parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "messages_org_idempotency_unique": { + "name": "messages_org_idempotency_unique", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"messages\".\"idempotency_key\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "messages_search_idx": { + "name": "messages_search_idx", + "columns": [ + { + "expression": "to_tsvector('english', \"plain_text\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "messages_organisation_id_organisations_id_fk": { + "name": "messages_organisation_id_organisations_id_fk", + "tableFrom": "messages", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "messages_room_id_rooms_id_fk": { + "name": "messages_room_id_rooms_id_fk", + "tableFrom": "messages", + "tableTo": "rooms", + "columnsFrom": [ + "room_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "messages_author_actor_id_actors_id_fk": { + "name": "messages_author_actor_id_actors_id_fk", + "tableFrom": "messages", + "tableTo": "actors", + "columnsFrom": [ + "author_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "messages_related_alert_id_alerts_id_fk": { + "name": "messages_related_alert_id_alerts_id_fk", + "tableFrom": "messages", + "tableTo": "alerts", + "columnsFrom": [ + "related_alert_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "messages_related_investigation_id_investigations_id_fk": { + "name": "messages_related_investigation_id_investigations_id_fk", + "tableFrom": "messages", + "tableTo": "investigations", + "columnsFrom": [ + "related_investigation_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": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "actor_id": { + "name": "actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "safe_preview": { + "name": "safe_preview", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target": { + "name": "target", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "read_at": { + "name": "read_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": { + "notifications_org_actor_read_idx": { + "name": "notifications_org_actor_read_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "actor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "read_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "notifications_organisation_id_organisations_id_fk": { + "name": "notifications_organisation_id_organisations_id_fk", + "tableFrom": "notifications", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "notifications_actor_id_actors_id_fk": { + "name": "notifications_actor_id_actors_id_fk", + "tableFrom": "notifications", + "tableTo": "actors", + "columnsFrom": [ + "actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.operational_knowledge": { + "name": "operational_knowledge", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'proposed'" + }, + "classification": { + "name": "classification", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'internal'" + }, + "evidence_references": { + "name": "evidence_references", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "source_installation_id": { + "name": "source_installation_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "proposed_by_actor_id": { + "name": "proposed_by_actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "supersedes_id": { + "name": "supersedes_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "reviewed_by_actor_id": { + "name": "reviewed_by_actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "reviewed_at": { + "name": "reviewed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "review_reason": { + "name": "review_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "policy_decision": { + "name": "policy_decision", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending_review'" + }, + "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": { + "operational_knowledge_org_status_idx": { + "name": "operational_knowledge_org_status_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "operational_knowledge_org_hash_idx": { + "name": "operational_knowledge_org_hash_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "content_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "operational_knowledge_organisation_id_organisations_id_fk": { + "name": "operational_knowledge_organisation_id_organisations_id_fk", + "tableFrom": "operational_knowledge", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "operational_knowledge_source_installation_id_mcp_installations_id_fk": { + "name": "operational_knowledge_source_installation_id_mcp_installations_id_fk", + "tableFrom": "operational_knowledge", + "tableTo": "mcp_installations", + "columnsFrom": [ + "source_installation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "operational_knowledge_proposed_by_actor_id_actors_id_fk": { + "name": "operational_knowledge_proposed_by_actor_id_actors_id_fk", + "tableFrom": "operational_knowledge", + "tableTo": "actors", + "columnsFrom": [ + "proposed_by_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "operational_knowledge_reviewed_by_actor_id_actors_id_fk": { + "name": "operational_knowledge_reviewed_by_actor_id_actors_id_fk", + "tableFrom": "operational_knowledge", + "tableTo": "actors", + "columnsFrom": [ + "reviewed_by_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "operational_knowledge_proposer_org_fk": { + "name": "operational_knowledge_proposer_org_fk", + "tableFrom": "operational_knowledge", + "tableTo": "actors", + "columnsFrom": [ + "proposed_by_actor_id", + "organisation_id" + ], + "columnsTo": [ + "id", + "organisation_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "operational_knowledge_kind_check": { + "name": "operational_knowledge_kind_check", + "value": "\"operational_knowledge\".\"kind\" in ('fact','finding','correction','procedure')" + }, + "operational_knowledge_status_check": { + "name": "operational_knowledge_status_check", + "value": "\"operational_knowledge\".\"status\" in ('proposed','accepted','quarantined','rejected','superseded','expired')" + }, + "operational_knowledge_policy_check": { + "name": "operational_knowledge_policy_check", + "value": "\"operational_knowledge\".\"policy_decision\" in ('accepted','quarantined','rejected','pending_review')" + }, + "operational_knowledge_classification_check": { + "name": "operational_knowledge_classification_check", + "value": "\"operational_knowledge\".\"classification\" in ('public','internal','confidential','restricted')" + } + }, + "isRLSEnabled": false + }, + "public.organisations": { + "name": "organisations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data_region": { + "name": "data_region", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'australia'" + }, + "default_timezone": { + "name": "default_timezone", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'Australia/Sydney'" + }, + "retention_policy": { + "name": "retention_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "authentication_policy": { + "name": "authentication_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "organisations_slug_unique": { + "name": "organisations_slug_unique", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "organisations_status_check": { + "name": "organisations_status_check", + "value": "\"organisations\".\"status\" in ('active','suspended')" + } + }, + "isRLSEnabled": false + }, + "public.outbox_events": { + "name": "outbox_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "aggregate_type": { + "name": "aggregate_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "aggregate_id": { + "name": "aggregate_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "queue_name": { + "name": "queue_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trace_id": { + "name": "trace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "available_at": { + "name": "available_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "dispatched_at": { + "name": "dispatched_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "outbox_idempotency_unique": { + "name": "outbox_idempotency_unique", + "columns": [ + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "outbox_pending_idx": { + "name": "outbox_pending_idx", + "columns": [ + { + "expression": "available_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"outbox_events\".\"dispatched_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "outbox_events_organisation_id_organisations_id_fk": { + "name": "outbox_events_organisation_id_organisations_id_fk", + "tableFrom": "outbox_events", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.reaction_operations": { + "name": "reaction_operations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "actor_id": { + "name": "actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "emoji": { + "name": "emoji", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "result_count": { + "name": "result_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "reaction_operations_org_idempotency_unique": { + "name": "reaction_operations_org_idempotency_unique", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "reaction_operations_org_message_idx": { + "name": "reaction_operations_org_message_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "reaction_operations_organisation_id_organisations_id_fk": { + "name": "reaction_operations_organisation_id_organisations_id_fk", + "tableFrom": "reaction_operations", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "reaction_operations_message_id_messages_id_fk": { + "name": "reaction_operations_message_id_messages_id_fk", + "tableFrom": "reaction_operations", + "tableTo": "messages", + "columnsFrom": [ + "message_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "reaction_operations_actor_id_actors_id_fk": { + "name": "reaction_operations_actor_id_actors_id_fk", + "tableFrom": "reaction_operations", + "tableTo": "actors", + "columnsFrom": [ + "actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.reaction_pack_assets": { + "name": "reaction_pack_assets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision_id": { + "name": "revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "alt_text": { + "name": "alt_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "byte_size": { + "name": "byte_size", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "width": { + "name": "width", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "height": { + "name": "height", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "frame_count": { + "name": "frame_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "sha256": { + "name": "sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "storage_key": { + "name": "storage_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "verification_state": { + "name": "verification_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'verified'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "reaction_pack_assets_org_revision_name_unique": { + "name": "reaction_pack_assets_org_revision_name_unique", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "reaction_pack_assets_org_digest_idx": { + "name": "reaction_pack_assets_org_digest_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "reaction_pack_assets_organisation_id_organisations_id_fk": { + "name": "reaction_pack_assets_organisation_id_organisations_id_fk", + "tableFrom": "reaction_pack_assets", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "reaction_pack_assets_revision_id_reaction_pack_revisions_id_fk": { + "name": "reaction_pack_assets_revision_id_reaction_pack_revisions_id_fk", + "tableFrom": "reaction_pack_assets", + "tableTo": "reaction_pack_revisions", + "columnsFrom": [ + "revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "reaction_pack_assets_verification_check": { + "name": "reaction_pack_assets_verification_check", + "value": "\"reaction_pack_assets\".\"verification_state\" in ('verified','missing','mismatch')" + }, + "reaction_pack_assets_dimensions_check": { + "name": "reaction_pack_assets_dimensions_check", + "value": "\"reaction_pack_assets\".\"width\" > 0 and \"reaction_pack_assets\".\"height\" > 0 and \"reaction_pack_assets\".\"frame_count\" > 0 and \"reaction_pack_assets\".\"byte_size\" > 0" + } + }, + "isRLSEnabled": false + }, + "public.reaction_pack_revisions": { + "name": "reaction_pack_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pack_id": { + "name": "pack_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approved_by_actor_id": { + "name": "approved_by_actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approved_at": { + "name": "approved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "superseded_at": { + "name": "superseded_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "removed_at": { + "name": "removed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by_actor_id": { + "name": "created_by_actor_id", + "type": "uuid", + "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": { + "reaction_pack_revisions_org_pack_revision_unique": { + "name": "reaction_pack_revisions_org_pack_revision_unique", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pack_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "reaction_pack_revisions_org_status_idx": { + "name": "reaction_pack_revisions_org_status_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "reaction_pack_revisions_organisation_id_organisations_id_fk": { + "name": "reaction_pack_revisions_organisation_id_organisations_id_fk", + "tableFrom": "reaction_pack_revisions", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "reaction_pack_revisions_pack_id_reaction_packs_id_fk": { + "name": "reaction_pack_revisions_pack_id_reaction_packs_id_fk", + "tableFrom": "reaction_pack_revisions", + "tableTo": "reaction_packs", + "columnsFrom": [ + "pack_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "reaction_pack_revisions_approved_by_actor_id_actors_id_fk": { + "name": "reaction_pack_revisions_approved_by_actor_id_actors_id_fk", + "tableFrom": "reaction_pack_revisions", + "tableTo": "actors", + "columnsFrom": [ + "approved_by_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "reaction_pack_revisions_created_by_actor_id_actors_id_fk": { + "name": "reaction_pack_revisions_created_by_actor_id_actors_id_fk", + "tableFrom": "reaction_pack_revisions", + "tableTo": "actors", + "columnsFrom": [ + "created_by_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "reaction_pack_revisions_status_check": { + "name": "reaction_pack_revisions_status_check", + "value": "\"reaction_pack_revisions\".\"status\" in ('draft','approved','superseded','removed')" + }, + "reaction_pack_revisions_revision_check": { + "name": "reaction_pack_revisions_revision_check", + "value": "\"reaction_pack_revisions\".\"revision\" > 0" + } + }, + "isRLSEnabled": false + }, + "public.reaction_packs": { + "name": "reaction_packs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lifecycle": { + "name": "lifecycle", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "created_by_actor_id": { + "name": "created_by_actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "removed_by_actor_id": { + "name": "removed_by_actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "removed_at": { + "name": "removed_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": { + "reaction_packs_org_slug_unique": { + "name": "reaction_packs_org_slug_unique", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "reaction_packs_org_lifecycle_idx": { + "name": "reaction_packs_org_lifecycle_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lifecycle", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "reaction_packs_organisation_id_organisations_id_fk": { + "name": "reaction_packs_organisation_id_organisations_id_fk", + "tableFrom": "reaction_packs", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "reaction_packs_created_by_actor_id_actors_id_fk": { + "name": "reaction_packs_created_by_actor_id_actors_id_fk", + "tableFrom": "reaction_packs", + "tableTo": "actors", + "columnsFrom": [ + "created_by_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "reaction_packs_removed_by_actor_id_actors_id_fk": { + "name": "reaction_packs_removed_by_actor_id_actors_id_fk", + "tableFrom": "reaction_packs", + "tableTo": "actors", + "columnsFrom": [ + "removed_by_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "reaction_packs_lifecycle_check": { + "name": "reaction_packs_lifecycle_check", + "value": "\"reaction_packs\".\"lifecycle\" in ('active','removed')" + } + }, + "isRLSEnabled": false + }, + "public.reactions": { + "name": "reactions", + "schema": "", + "columns": { + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "actor_id": { + "name": "actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "emoji": { + "name": "emoji", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "reactions_organisation_id_organisations_id_fk": { + "name": "reactions_organisation_id_organisations_id_fk", + "tableFrom": "reactions", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "reactions_message_id_messages_id_fk": { + "name": "reactions_message_id_messages_id_fk", + "tableFrom": "reactions", + "tableTo": "messages", + "columnsFrom": [ + "message_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "reactions_actor_id_actors_id_fk": { + "name": "reactions_actor_id_actors_id_fk", + "tableFrom": "reactions", + "tableTo": "actors", + "columnsFrom": [ + "actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "reactions_message_id_actor_id_emoji_pk": { + "name": "reactions_message_id_actor_id_emoji_pk", + "columns": [ + "message_id", + "actor_id", + "emoji" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.report_deliveries": { + "name": "report_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "report_id": { + "name": "report_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "requested_by_actor_id": { + "name": "requested_by_actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "recipient": { + "name": "recipient", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'awaiting_approval'" + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "delivered_at": { + "name": "delivered_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": { + "report_deliveries_org_idempotency_unique": { + "name": "report_deliveries_org_idempotency_unique", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "report_deliveries_org_report_status_idx": { + "name": "report_deliveries_org_report_status_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "report_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "report_deliveries_organisation_id_organisations_id_fk": { + "name": "report_deliveries_organisation_id_organisations_id_fk", + "tableFrom": "report_deliveries", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "report_deliveries_report_id_report_manifests_id_fk": { + "name": "report_deliveries_report_id_report_manifests_id_fk", + "tableFrom": "report_deliveries", + "tableTo": "report_manifests", + "columnsFrom": [ + "report_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "report_deliveries_approval_id_approvals_id_fk": { + "name": "report_deliveries_approval_id_approvals_id_fk", + "tableFrom": "report_deliveries", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "report_deliveries_requested_by_actor_id_actors_id_fk": { + "name": "report_deliveries_requested_by_actor_id_actors_id_fk", + "tableFrom": "report_deliveries", + "tableTo": "actors", + "columnsFrom": [ + "requested_by_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "report_deliveries_status_check": { + "name": "report_deliveries_status_check", + "value": "\"report_deliveries\".\"status\" in ('awaiting_approval','queued','delivered','failed','cancelled')" + } + }, + "isRLSEnabled": false + }, + "public.report_manifests": { + "name": "report_manifests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_run_id": { + "name": "agent_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "task_id": { + "name": "task_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "room_id": { + "name": "room_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "requested_by_actor_id": { + "name": "requested_by_actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "manifest": { + "name": "manifest", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "classification": { + "name": "classification", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'internal'" + }, + "review_note": { + "name": "review_note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "posted_message_id": { + "name": "posted_message_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reviewed_at": { + "name": "reviewed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_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": { + "report_manifests_org_idempotency_unique": { + "name": "report_manifests_org_idempotency_unique", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "report_manifests_org_room_status_idx": { + "name": "report_manifests_org_room_status_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "room_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "report_manifests_organisation_id_organisations_id_fk": { + "name": "report_manifests_organisation_id_organisations_id_fk", + "tableFrom": "report_manifests", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "report_manifests_agent_run_id_agent_runs_id_fk": { + "name": "report_manifests_agent_run_id_agent_runs_id_fk", + "tableFrom": "report_manifests", + "tableTo": "agent_runs", + "columnsFrom": [ + "agent_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "report_manifests_task_id_tasks_id_fk": { + "name": "report_manifests_task_id_tasks_id_fk", + "tableFrom": "report_manifests", + "tableTo": "tasks", + "columnsFrom": [ + "task_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "report_manifests_room_id_rooms_id_fk": { + "name": "report_manifests_room_id_rooms_id_fk", + "tableFrom": "report_manifests", + "tableTo": "rooms", + "columnsFrom": [ + "room_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "report_manifests_requested_by_actor_id_actors_id_fk": { + "name": "report_manifests_requested_by_actor_id_actors_id_fk", + "tableFrom": "report_manifests", + "tableTo": "actors", + "columnsFrom": [ + "requested_by_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "report_manifests_posted_message_id_messages_id_fk": { + "name": "report_manifests_posted_message_id_messages_id_fk", + "tableFrom": "report_manifests", + "tableTo": "messages", + "columnsFrom": [ + "posted_message_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "report_manifests_status_check": { + "name": "report_manifests_status_check", + "value": "\"report_manifests\".\"status\" in ('draft','reviewed','posted','superseded')" + }, + "report_manifests_version_check": { + "name": "report_manifests_version_check", + "value": "\"report_manifests\".\"version\" > 0" + } + }, + "isRLSEnabled": false + }, + "public.report_schedules": { + "name": "report_schedules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "room_id": { + "name": "room_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by_actor_id": { + "name": "created_by_actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "cadence": { + "name": "cadence", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "audience": { + "name": "audience", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'leadership'" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "archived_at": { + "name": "archived_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": { + "report_schedules_org_idempotency_unique": { + "name": "report_schedules_org_idempotency_unique", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "report_schedules_org_due_idx": { + "name": "report_schedules_org_due_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "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": { + "report_schedules_organisation_id_organisations_id_fk": { + "name": "report_schedules_organisation_id_organisations_id_fk", + "tableFrom": "report_schedules", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "report_schedules_room_id_rooms_id_fk": { + "name": "report_schedules_room_id_rooms_id_fk", + "tableFrom": "report_schedules", + "tableTo": "rooms", + "columnsFrom": [ + "room_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "report_schedules_created_by_actor_id_actors_id_fk": { + "name": "report_schedules_created_by_actor_id_actors_id_fk", + "tableFrom": "report_schedules", + "tableTo": "actors", + "columnsFrom": [ + "created_by_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "report_schedules_cadence_check": { + "name": "report_schedules_cadence_check", + "value": "\"report_schedules\".\"cadence\" in ('weekly','monthly')" + }, + "report_schedules_audience_check": { + "name": "report_schedules_audience_check", + "value": "\"report_schedules\".\"audience\" in ('analyst','leadership','executive')" + } + }, + "isRLSEnabled": false + }, + "public.research_items": { + "name": "research_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "watchlist_id": { + "name": "watchlist_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "research_run_id": { + "name": "research_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "fingerprint": { + "name": "fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_url": { + "name": "source_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_published_at": { + "name": "source_published_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "root_message_id": { + "name": "root_message_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "latest_message_id": { + "name": "latest_message_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "brief": { + "name": "brief", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "feedback": { + "name": "feedback", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feedback_by_actor_id": { + "name": "feedback_by_actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "feedback_at": { + "name": "feedback_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": { + "research_items_org_fingerprint_unique": { + "name": "research_items_org_fingerprint_unique", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "research_items_org_watchlist_idx": { + "name": "research_items_org_watchlist_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "watchlist_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "research_items_organisation_id_organisations_id_fk": { + "name": "research_items_organisation_id_organisations_id_fk", + "tableFrom": "research_items", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "research_items_watchlist_id_research_watchlists_id_fk": { + "name": "research_items_watchlist_id_research_watchlists_id_fk", + "tableFrom": "research_items", + "tableTo": "research_watchlists", + "columnsFrom": [ + "watchlist_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "research_items_research_run_id_research_runs_id_fk": { + "name": "research_items_research_run_id_research_runs_id_fk", + "tableFrom": "research_items", + "tableTo": "research_runs", + "columnsFrom": [ + "research_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "research_items_root_message_id_messages_id_fk": { + "name": "research_items_root_message_id_messages_id_fk", + "tableFrom": "research_items", + "tableTo": "messages", + "columnsFrom": [ + "root_message_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "research_items_latest_message_id_messages_id_fk": { + "name": "research_items_latest_message_id_messages_id_fk", + "tableFrom": "research_items", + "tableTo": "messages", + "columnsFrom": [ + "latest_message_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "research_items_feedback_by_actor_id_actors_id_fk": { + "name": "research_items_feedback_by_actor_id_actors_id_fk", + "tableFrom": "research_items", + "tableTo": "actors", + "columnsFrom": [ + "feedback_by_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.research_runs": { + "name": "research_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "watchlist_id": { + "name": "watchlist_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_run_id": { + "name": "agent_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "source_limit": { + "name": "source_limit", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token_budget": { + "name": "token_budget", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "cost_limit_cents": { + "name": "cost_limit_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "time_limit_seconds": { + "name": "time_limit_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "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": { + "research_runs_org_idempotency_unique": { + "name": "research_runs_org_idempotency_unique", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "research_runs_org_agent_unique": { + "name": "research_runs_org_agent_unique", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "research_runs_org_status_idx": { + "name": "research_runs_org_status_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "research_runs_organisation_id_organisations_id_fk": { + "name": "research_runs_organisation_id_organisations_id_fk", + "tableFrom": "research_runs", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "research_runs_watchlist_id_research_watchlists_id_fk": { + "name": "research_runs_watchlist_id_research_watchlists_id_fk", + "tableFrom": "research_runs", + "tableTo": "research_watchlists", + "columnsFrom": [ + "watchlist_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "research_runs_agent_run_id_agent_runs_id_fk": { + "name": "research_runs_agent_run_id_agent_runs_id_fk", + "tableFrom": "research_runs", + "tableTo": "agent_runs", + "columnsFrom": [ + "agent_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "research_runs_status_check": { + "name": "research_runs_status_check", + "value": "\"research_runs\".\"status\" in ('queued','running','completed','failed')" + } + }, + "isRLSEnabled": false + }, + "public.research_watchlists": { + "name": "research_watchlists", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "room_id": { + "name": "room_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by_actor_id": { + "name": "created_by_actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vendors": { + "name": "vendors", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "technologies": { + "name": "technologies", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "sources": { + "name": "sources", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "cadence_minutes": { + "name": "cadence_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 240 + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_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": { + "research_watchlists_org_name_unique": { + "name": "research_watchlists_org_name_unique", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "research_watchlists_due_idx": { + "name": "research_watchlists_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": { + "research_watchlists_organisation_id_organisations_id_fk": { + "name": "research_watchlists_organisation_id_organisations_id_fk", + "tableFrom": "research_watchlists", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "research_watchlists_room_id_rooms_id_fk": { + "name": "research_watchlists_room_id_rooms_id_fk", + "tableFrom": "research_watchlists", + "tableTo": "rooms", + "columnsFrom": [ + "room_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "research_watchlists_created_by_actor_id_actors_id_fk": { + "name": "research_watchlists_created_by_actor_id_actors_id_fk", + "tableFrom": "research_watchlists", + "tableTo": "actors", + "columnsFrom": [ + "created_by_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "research_watchlists_cadence_check": { + "name": "research_watchlists_cadence_check", + "value": "\"research_watchlists\".\"cadence_minutes\" between 15 and 10080" + } + }, + "isRLSEnabled": false + }, + "public.room_integration_bindings": { + "name": "room_integration_bindings", + "schema": "", + "columns": { + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "room_id": { + "name": "room_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "integration_id": { + "name": "integration_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by_actor_id": { + "name": "created_by_actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "room_integration_bindings_org_room_idx": { + "name": "room_integration_bindings_org_room_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "room_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "room_integration_bindings_organisation_id_organisations_id_fk": { + "name": "room_integration_bindings_organisation_id_organisations_id_fk", + "tableFrom": "room_integration_bindings", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "room_integration_bindings_room_id_rooms_id_fk": { + "name": "room_integration_bindings_room_id_rooms_id_fk", + "tableFrom": "room_integration_bindings", + "tableTo": "rooms", + "columnsFrom": [ + "room_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "room_integration_bindings_integration_id_integration_records_id_fk": { + "name": "room_integration_bindings_integration_id_integration_records_id_fk", + "tableFrom": "room_integration_bindings", + "tableTo": "integration_records", + "columnsFrom": [ + "integration_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "room_integration_bindings_created_by_actor_id_actors_id_fk": { + "name": "room_integration_bindings_created_by_actor_id_actors_id_fk", + "tableFrom": "room_integration_bindings", + "tableTo": "actors", + "columnsFrom": [ + "created_by_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "room_integration_bindings_room_id_integration_id_pk": { + "name": "room_integration_bindings_room_id_integration_id_pk", + "columns": [ + "room_id", + "integration_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.room_invitations": { + "name": "room_invitations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "room_id": { + "name": "room_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "invited_actor_id": { + "name": "invited_actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "membership_role": { + "name": "membership_role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_expires_at": { + "name": "access_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "invited_by_actor_id": { + "name": "invited_by_actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "responded_at": { + "name": "responded_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "room_invitations_org_idempotency_unique": { + "name": "room_invitations_org_idempotency_unique", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "room_invitations_org_room_status_idx": { + "name": "room_invitations_org_room_status_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "room_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "room_invitations_organisation_id_organisations_id_fk": { + "name": "room_invitations_organisation_id_organisations_id_fk", + "tableFrom": "room_invitations", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "room_invitations_room_id_rooms_id_fk": { + "name": "room_invitations_room_id_rooms_id_fk", + "tableFrom": "room_invitations", + "tableTo": "rooms", + "columnsFrom": [ + "room_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "room_invitations_invited_actor_id_actors_id_fk": { + "name": "room_invitations_invited_actor_id_actors_id_fk", + "tableFrom": "room_invitations", + "tableTo": "actors", + "columnsFrom": [ + "invited_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "room_invitations_invited_by_actor_id_actors_id_fk": { + "name": "room_invitations_invited_by_actor_id_actors_id_fk", + "tableFrom": "room_invitations", + "tableTo": "actors", + "columnsFrom": [ + "invited_by_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "room_invitation_role_check": { + "name": "room_invitation_role_check", + "value": "\"room_invitations\".\"membership_role\" in ('moderator','member','guest','agent_member')" + }, + "room_invitation_status_check": { + "name": "room_invitation_status_check", + "value": "\"room_invitations\".\"status\" in ('pending','accepted','revoked','expired')" + } + }, + "isRLSEnabled": false + }, + "public.room_memberships": { + "name": "room_memberships", + "schema": "", + "columns": { + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "room_id": { + "name": "room_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "actor_id": { + "name": "actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "membership_role": { + "name": "membership_role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "notification_level": { + "name": "notification_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'all'" + }, + "notify_replies": { + "name": "notify_replies", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "notify_followed_threads": { + "name": "notify_followed_threads", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_read_event_id": { + "name": "last_read_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "muted": { + "name": "muted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "favourite": { + "name": "favourite", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "sidebar_position": { + "name": "sidebar_position", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "sidebar_group": { + "name": "sidebar_group", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_expires_at": { + "name": "access_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "room_memberships_org_actor_idx": { + "name": "room_memberships_org_actor_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "actor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "room_memberships_organisation_id_organisations_id_fk": { + "name": "room_memberships_organisation_id_organisations_id_fk", + "tableFrom": "room_memberships", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "room_memberships_room_id_rooms_id_fk": { + "name": "room_memberships_room_id_rooms_id_fk", + "tableFrom": "room_memberships", + "tableTo": "rooms", + "columnsFrom": [ + "room_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "room_memberships_actor_id_actors_id_fk": { + "name": "room_memberships_actor_id_actors_id_fk", + "tableFrom": "room_memberships", + "tableTo": "actors", + "columnsFrom": [ + "actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "room_memberships_room_id_actor_id_pk": { + "name": "room_memberships_room_id_actor_id_pk", + "columns": [ + "room_id", + "actor_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "room_membership_role_check": { + "name": "room_membership_role_check", + "value": "\"room_memberships\".\"membership_role\" in ('owner','moderator','member','guest','agent_member')" + } + }, + "isRLSEnabled": false + }, + "public.rooms": { + "name": "rooms", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "room_type": { + "name": "room_type", + "type": "room_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "visibility": { + "name": "visibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'organisation'" + }, + "topic": { + "name": "topic", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "policies": { + "name": "policies", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "direct_fingerprint": { + "name": "direct_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_actor_id": { + "name": "created_by_actor_id", + "type": "uuid", + "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()" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "linked_investigation_id": { + "name": "linked_investigation_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "linked_kelpie_case_id": { + "name": "linked_kelpie_case_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "default_severity": { + "name": "default_severity", + "type": "severity", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "tlp": { + "name": "tlp", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'amber'" + }, + "retention_policy": { + "name": "retention_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + } + }, + "indexes": { + "rooms_org_slug_unique": { + "name": "rooms_org_slug_unique", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "rooms_org_direct_fingerprint_unique": { + "name": "rooms_org_direct_fingerprint_unique", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "direct_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"rooms\".\"direct_fingerprint\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "rooms_org_type_idx": { + "name": "rooms_org_type_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "room_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "rooms_organisation_id_organisations_id_fk": { + "name": "rooms_organisation_id_organisations_id_fk", + "tableFrom": "rooms", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "rooms_created_by_actor_id_actors_id_fk": { + "name": "rooms_created_by_actor_id_actors_id_fk", + "tableFrom": "rooms", + "tableTo": "actors", + "columnsFrom": [ + "created_by_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.slack_agent_exposures": { + "name": "slack_agent_exposures", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "installation_id": { + "name": "installation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "allowed_channel_ids": { + "name": "allowed_channel_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "allow_direct_messages": { + "name": "allow_direct_messages", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "allow_thread_context": { + "name": "allow_thread_context", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "updated_by_actor_id": { + "name": "updated_by_actor_id", + "type": "uuid", + "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": { + "slack_exposures_installation_agent_unique": { + "name": "slack_exposures_installation_agent_unique", + "columns": [ + { + "expression": "installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "slack_exposures_org_enabled_idx": { + "name": "slack_exposures_org_enabled_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "slack_agent_exposures_organisation_id_organisations_id_fk": { + "name": "slack_agent_exposures_organisation_id_organisations_id_fk", + "tableFrom": "slack_agent_exposures", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "slack_agent_exposures_installation_id_slack_installations_id_fk": { + "name": "slack_agent_exposures_installation_id_slack_installations_id_fk", + "tableFrom": "slack_agent_exposures", + "tableTo": "slack_installations", + "columnsFrom": [ + "installation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "slack_agent_exposures_agent_id_agent_definitions_id_fk": { + "name": "slack_agent_exposures_agent_id_agent_definitions_id_fk", + "tableFrom": "slack_agent_exposures", + "tableTo": "agent_definitions", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "slack_agent_exposures_updated_by_actor_id_actors_id_fk": { + "name": "slack_agent_exposures_updated_by_actor_id_actors_id_fk", + "tableFrom": "slack_agent_exposures", + "tableTo": "actors", + "columnsFrom": [ + "updated_by_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.slack_identity_mappings": { + "name": "slack_identity_mappings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "installation_id": { + "name": "installation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "slack_user_id": { + "name": "slack_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_id": { + "name": "actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "created_by_actor_id": { + "name": "created_by_actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "slack_identity_installation_user_unique": { + "name": "slack_identity_installation_user_unique", + "columns": [ + { + "expression": "installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slack_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "slack_identity_org_actor_unique": { + "name": "slack_identity_org_actor_unique", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "actor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "slack_identity_mappings_organisation_id_organisations_id_fk": { + "name": "slack_identity_mappings_organisation_id_organisations_id_fk", + "tableFrom": "slack_identity_mappings", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "slack_identity_mappings_installation_id_slack_installations_id_fk": { + "name": "slack_identity_mappings_installation_id_slack_installations_id_fk", + "tableFrom": "slack_identity_mappings", + "tableTo": "slack_installations", + "columnsFrom": [ + "installation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "slack_identity_mappings_actor_id_actors_id_fk": { + "name": "slack_identity_mappings_actor_id_actors_id_fk", + "tableFrom": "slack_identity_mappings", + "tableTo": "actors", + "columnsFrom": [ + "actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "slack_identity_mappings_created_by_actor_id_actors_id_fk": { + "name": "slack_identity_mappings_created_by_actor_id_actors_id_fk", + "tableFrom": "slack_identity_mappings", + "tableTo": "actors", + "columnsFrom": [ + "created_by_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.slack_inbox_events": { + "name": "slack_inbox_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "installation_id": { + "name": "installation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload_hash": { + "name": "payload_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "encrypted_payload": { + "name": "encrypted_payload", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "received_at": { + "name": "received_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "slack_inbox_installation_event_unique": { + "name": "slack_inbox_installation_event_unique", + "columns": [ + { + "expression": "installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "slack_inbox_org_status_idx": { + "name": "slack_inbox_org_status_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "received_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "slack_inbox_events_organisation_id_organisations_id_fk": { + "name": "slack_inbox_events_organisation_id_organisations_id_fk", + "tableFrom": "slack_inbox_events", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "slack_inbox_events_installation_id_slack_installations_id_fk": { + "name": "slack_inbox_events_installation_id_slack_installations_id_fk", + "tableFrom": "slack_inbox_events", + "tableTo": "slack_installations", + "columnsFrom": [ + "installation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.slack_installations": { + "name": "slack_installations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "team_name": { + "name": "team_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enterprise_id": { + "name": "enterprise_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bot_user_id": { + "name": "bot_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "encrypted_bot_token": { + "name": "encrypted_bot_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "encrypted_app_token": { + "name": "encrypted_app_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "installed_by_actor_id": { + "name": "installed_by_actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "installed_at": { + "name": "installed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_health_at": { + "name": "last_health_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_delivery_at": { + "name": "last_delivery_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_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": { + "slack_installations_team_unique": { + "name": "slack_installations_team_unique", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "slack_installations_org_status_idx": { + "name": "slack_installations_org_status_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "slack_installations_organisation_id_organisations_id_fk": { + "name": "slack_installations_organisation_id_organisations_id_fk", + "tableFrom": "slack_installations", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "slack_installations_installed_by_actor_id_actors_id_fk": { + "name": "slack_installations_installed_by_actor_id_actors_id_fk", + "tableFrom": "slack_installations", + "tableTo": "actors", + "columnsFrom": [ + "installed_by_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.slack_run_deliveries": { + "name": "slack_run_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "installation_id": { + "name": "installation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "inbox_event_id": { + "name": "inbox_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "thread_ts": { + "name": "thread_ts", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "progress_message_ts": { + "name": "progress_message_ts", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_message_ts": { + "name": "result_message_ts", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "last_progress": { + "name": "last_progress", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_error": { + "name": "last_error", + "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": { + "slack_delivery_installation_run_unique": { + "name": "slack_delivery_installation_run_unique", + "columns": [ + { + "expression": "installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "slack_delivery_org_status_idx": { + "name": "slack_delivery_org_status_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "slack_run_deliveries_organisation_id_organisations_id_fk": { + "name": "slack_run_deliveries_organisation_id_organisations_id_fk", + "tableFrom": "slack_run_deliveries", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "slack_run_deliveries_installation_id_slack_installations_id_fk": { + "name": "slack_run_deliveries_installation_id_slack_installations_id_fk", + "tableFrom": "slack_run_deliveries", + "tableTo": "slack_installations", + "columnsFrom": [ + "installation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "slack_run_deliveries_run_id_agent_runs_id_fk": { + "name": "slack_run_deliveries_run_id_agent_runs_id_fk", + "tableFrom": "slack_run_deliveries", + "tableTo": "agent_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "slack_run_deliveries_inbox_event_id_slack_inbox_events_id_fk": { + "name": "slack_run_deliveries_inbox_event_id_slack_inbox_events_id_fk", + "tableFrom": "slack_run_deliveries", + "tableTo": "slack_inbox_events", + "columnsFrom": [ + "inbox_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.synthetic_artifact_provenance": { + "name": "synthetic_artifact_provenance", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "artifact_table": { + "name": "artifact_table", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "artifact_id": { + "name": "artifact_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_reference": { + "name": "source_reference", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "recorded_by_actor_id": { + "name": "recorded_by_actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "recorded_at": { + "name": "recorded_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "synthetic_artifact_provenance_artifact_unique": { + "name": "synthetic_artifact_provenance_artifact_unique", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "artifact_table", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "artifact_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "synthetic_artifact_provenance_organisation_id_organisations_id_fk": { + "name": "synthetic_artifact_provenance_organisation_id_organisations_id_fk", + "tableFrom": "synthetic_artifact_provenance", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "synthetic_artifact_provenance_recorded_by_actor_id_actors_id_fk": { + "name": "synthetic_artifact_provenance_recorded_by_actor_id_actors_id_fk", + "tableFrom": "synthetic_artifact_provenance", + "tableTo": "actors", + "columnsFrom": [ + "recorded_by_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "synthetic_artifact_provenance_table_check": { + "name": "synthetic_artifact_provenance_table_check", + "value": "\"synthetic_artifact_provenance\".\"artifact_table\" in ('rooms','tasks','hunts','integrations','researchWatchlists','reportManifests','reportSchedules','messages','evidence','agentMemories','actors')" + }, + "synthetic_artifact_provenance_source_check": { + "name": "synthetic_artifact_provenance_source_check", + "value": "\"synthetic_artifact_provenance\".\"source_kind\" in ('seed_fixture','mock_runtime','test_fixture','legacy_live_proof')" + } + }, + "isRLSEnabled": false + }, + "public.synthetic_cleanup_object_deletion_attempts": { + "name": "synthetic_cleanup_object_deletion_attempts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "manifest_id": { + "name": "manifest_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "evidence_id": { + "name": "evidence_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "version_id": { + "name": "version_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "authorization_approval_id": { + "name": "authorization_approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "result": { + "name": "result", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "attempted_by_actor_id": { + "name": "attempted_by_actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trace_id": { + "name": "trace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "attempted_at": { + "name": "attempted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "synthetic_cleanup_object_attempts_manifest_idx": { + "name": "synthetic_cleanup_object_attempts_manifest_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "manifest_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "attempted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "synthetic_cleanup_object_deletion_attempts_manifest_id_synthetic_cleanup_receipts_manifest_id_fk": { + "name": "synthetic_cleanup_object_deletion_attempts_manifest_id_synthetic_cleanup_receipts_manifest_id_fk", + "tableFrom": "synthetic_cleanup_object_deletion_attempts", + "tableTo": "synthetic_cleanup_receipts", + "columnsFrom": [ + "manifest_id" + ], + "columnsTo": [ + "manifest_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "synthetic_cleanup_object_deletion_attempts_organisation_id_organisations_id_fk": { + "name": "synthetic_cleanup_object_deletion_attempts_organisation_id_organisations_id_fk", + "tableFrom": "synthetic_cleanup_object_deletion_attempts", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "synthetic_cleanup_object_deletion_attempts_authorization_approval_id_approvals_id_fk": { + "name": "synthetic_cleanup_object_deletion_attempts_authorization_approval_id_approvals_id_fk", + "tableFrom": "synthetic_cleanup_object_deletion_attempts", + "tableTo": "approvals", + "columnsFrom": [ + "authorization_approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "synthetic_cleanup_object_deletion_attempts_attempted_by_actor_id_actors_id_fk": { + "name": "synthetic_cleanup_object_deletion_attempts_attempted_by_actor_id_actors_id_fk", + "tableFrom": "synthetic_cleanup_object_deletion_attempts", + "tableTo": "actors", + "columnsFrom": [ + "attempted_by_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "synthetic_cleanup_object_attempts_result_check": { + "name": "synthetic_cleanup_object_attempts_result_check", + "value": "\"synthetic_cleanup_object_deletion_attempts\".\"result\" in ('started','succeeded','failed','observed_missing')" + } + }, + "isRLSEnabled": false + }, + "public.synthetic_cleanup_receipts": { + "name": "synthetic_cleanup_receipts", + "schema": "", + "columns": { + "manifest_id": { + "name": "manifest_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "maintenance_actor_id": { + "name": "maintenance_actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "manifest_digest": { + "name": "manifest_digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "manifest": { + "name": "manifest", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "candidate_counts": { + "name": "candidate_counts", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "pre_digests": { + "name": "pre_digests", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "post_digests": { + "name": "post_digests", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "object_storage_objects": { + "name": "object_storage_objects", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "trace_id": { + "name": "trace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applied_at": { + "name": "applied_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "synthetic_cleanup_receipts_approval_unique": { + "name": "synthetic_cleanup_receipts_approval_unique", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "synthetic_cleanup_receipts_org_digest_unique": { + "name": "synthetic_cleanup_receipts_org_digest_unique", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "manifest_digest", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "synthetic_cleanup_receipts_organisation_id_organisations_id_fk": { + "name": "synthetic_cleanup_receipts_organisation_id_organisations_id_fk", + "tableFrom": "synthetic_cleanup_receipts", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "synthetic_cleanup_receipts_approval_id_approvals_id_fk": { + "name": "synthetic_cleanup_receipts_approval_id_approvals_id_fk", + "tableFrom": "synthetic_cleanup_receipts", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "synthetic_cleanup_receipts_maintenance_actor_id_actors_id_fk": { + "name": "synthetic_cleanup_receipts_maintenance_actor_id_actors_id_fk", + "tableFrom": "synthetic_cleanup_receipts", + "tableTo": "actors", + "columnsFrom": [ + "maintenance_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tasks": { + "name": "tasks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "status": { + "name": "status", + "type": "task_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'backlog'" + }, + "priority": { + "name": "priority", + "type": "task_priority", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'normal'" + }, + "assigned_actor_id": { + "name": "assigned_actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_actor_id": { + "name": "created_by_actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "room_id": { + "name": "room_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "investigation_id": { + "name": "investigation_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "related_case_id": { + "name": "related_case_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "approval_required": { + "name": "approval_required", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "due_at": { + "name": "due_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "agent_run_id": { + "name": "agent_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_run_status": { + "name": "agent_run_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_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": { + "tasks_org_status_idx": { + "name": "tasks_org_status_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasks_org_assignee_idx": { + "name": "tasks_org_assignee_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assigned_actor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasks_org_idempotency_unique": { + "name": "tasks_org_idempotency_unique", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tasks_organisation_id_organisations_id_fk": { + "name": "tasks_organisation_id_organisations_id_fk", + "tableFrom": "tasks", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tasks_assigned_actor_id_actors_id_fk": { + "name": "tasks_assigned_actor_id_actors_id_fk", + "tableFrom": "tasks", + "tableTo": "actors", + "columnsFrom": [ + "assigned_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tasks_created_by_actor_id_actors_id_fk": { + "name": "tasks_created_by_actor_id_actors_id_fk", + "tableFrom": "tasks", + "tableTo": "actors", + "columnsFrom": [ + "created_by_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tasks_room_id_rooms_id_fk": { + "name": "tasks_room_id_rooms_id_fk", + "tableFrom": "tasks", + "tableTo": "rooms", + "columnsFrom": [ + "room_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tasks_investigation_id_investigations_id_fk": { + "name": "tasks_investigation_id_investigations_id_fk", + "tableFrom": "tasks", + "tableTo": "investigations", + "columnsFrom": [ + "investigation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.thread_follows": { + "name": "thread_follows", + "schema": "", + "columns": { + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "root_message_id": { + "name": "root_message_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "actor_id": { + "name": "actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "thread_follows_org_actor_idx": { + "name": "thread_follows_org_actor_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "actor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "thread_follows_organisation_id_organisations_id_fk": { + "name": "thread_follows_organisation_id_organisations_id_fk", + "tableFrom": "thread_follows", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "thread_follows_root_message_id_messages_id_fk": { + "name": "thread_follows_root_message_id_messages_id_fk", + "tableFrom": "thread_follows", + "tableTo": "messages", + "columnsFrom": [ + "root_message_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "thread_follows_actor_id_actors_id_fk": { + "name": "thread_follows_actor_id_actors_id_fk", + "tableFrom": "thread_follows", + "tableTo": "actors", + "columnsFrom": [ + "actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "thread_follows_root_message_id_actor_id_pk": { + "name": "thread_follows_root_message_id_actor_id_pk", + "columns": [ + "root_message_id", + "actor_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.timeline_events": { + "name": "timeline_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "investigation_id": { + "name": "investigation_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "room_id": { + "name": "room_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "external_case_id": { + "name": "external_case_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_id": { + "name": "actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "occurred_at": { + "name": "occurred_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": { + "timeline_org_investigation_time_idx": { + "name": "timeline_org_investigation_time_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "investigation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "timeline_events_organisation_id_organisations_id_fk": { + "name": "timeline_events_organisation_id_organisations_id_fk", + "tableFrom": "timeline_events", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "timeline_events_investigation_id_investigations_id_fk": { + "name": "timeline_events_investigation_id_investigations_id_fk", + "tableFrom": "timeline_events", + "tableTo": "investigations", + "columnsFrom": [ + "investigation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "timeline_events_room_id_rooms_id_fk": { + "name": "timeline_events_room_id_rooms_id_fk", + "tableFrom": "timeline_events", + "tableTo": "rooms", + "columnsFrom": [ + "room_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "timeline_events_actor_id_actors_id_fk": { + "name": "timeline_events_actor_id_actors_id_fk", + "tableFrom": "timeline_events", + "tableTo": "actors", + "columnsFrom": [ + "actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "better_auth_user_id": { + "name": "better_auth_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "avatar": { + "name": "avatar", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "job_title": { + "name": "job_title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "team": { + "name": "team", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "presence_state": { + "name": "presence_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'offline'" + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'Australia/Sydney'" + }, + "notification_preferences": { + "name": "notification_preferences", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_active_at": { + "name": "last_active_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "users_org_email_unique": { + "name": "users_org_email_unique", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "users_better_auth_unique": { + "name": "users_better_auth_unique", + "columns": [ + { + "expression": "better_auth_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "users_org_idx": { + "name": "users_org_idx", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "users_organisation_id_organisations_id_fk": { + "name": "users_organisation_id_organisations_id_fk", + "tableFrom": "users", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "users_better_auth_user_id_auth_user_id_fk": { + "name": "users_better_auth_user_id_auth_user_id_fk", + "tableFrom": "users", + "tableTo": "auth_user", + "columnsFrom": [ + "better_auth_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_definitions": { + "name": "workflow_definitions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "workflow_key": { + "name": "workflow_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "yaml": { + "name": "yaml", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parsed": { + "name": "parsed", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "owner_actor_id": { + "name": "owner_actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "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": { + "workflow_defs_org_key_version_unique": { + "name": "workflow_defs_org_key_version_unique", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_definitions_organisation_id_organisations_id_fk": { + "name": "workflow_definitions_organisation_id_organisations_id_fk", + "tableFrom": "workflow_definitions", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workflow_definitions_owner_actor_id_actors_id_fk": { + "name": "workflow_definitions_owner_actor_id_actors_id_fk", + "tableFrom": "workflow_definitions", + "tableTo": "actors", + "columnsFrom": [ + "owner_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_runs": { + "name": "workflow_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "organisation_id": { + "name": "organisation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "workflow_definition_id": { + "name": "workflow_definition_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "trigger_event_id": { + "name": "trigger_event_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "room_id": { + "name": "room_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "investigation_id": { + "name": "investigation_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_by_actor_id": { + "name": "requested_by_actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "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 + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "workflow_runs_org_idempotency_unique": { + "name": "workflow_runs_org_idempotency_unique", + "columns": [ + { + "expression": "organisation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_runs_organisation_id_organisations_id_fk": { + "name": "workflow_runs_organisation_id_organisations_id_fk", + "tableFrom": "workflow_runs", + "tableTo": "organisations", + "columnsFrom": [ + "organisation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workflow_runs_workflow_definition_id_workflow_definitions_id_fk": { + "name": "workflow_runs_workflow_definition_id_workflow_definitions_id_fk", + "tableFrom": "workflow_runs", + "tableTo": "workflow_definitions", + "columnsFrom": [ + "workflow_definition_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workflow_runs_room_id_rooms_id_fk": { + "name": "workflow_runs_room_id_rooms_id_fk", + "tableFrom": "workflow_runs", + "tableTo": "rooms", + "columnsFrom": [ + "room_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workflow_runs_investigation_id_investigations_id_fk": { + "name": "workflow_runs_investigation_id_investigations_id_fk", + "tableFrom": "workflow_runs", + "tableTo": "investigations", + "columnsFrom": [ + "investigation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workflow_runs_requested_by_actor_id_actors_id_fk": { + "name": "workflow_runs_requested_by_actor_id_actors_id_fk", + "tableFrom": "workflow_runs", + "tableTo": "actors", + "columnsFrom": [ + "requested_by_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.actor_type": { + "name": "actor_type", + "schema": "public", + "values": [ + "human", + "agent", + "product", + "service", + "system" + ] + }, + "public.alert_status": { + "name": "alert_status", + "schema": "public", + "values": [ + "new", + "acknowledged", + "investigating", + "dismissed", + "promoted", + "closed" + ] + }, + "public.approval_status": { + "name": "approval_status", + "schema": "public", + "values": [ + "pending", + "approved", + "rejected", + "expired", + "cancelled", + "executed", + "failed" + ] + }, + "public.investigation_status": { + "name": "investigation_status", + "schema": "public", + "values": [ + "open", + "triaging", + "investigating", + "awaiting_approval", + "promoted", + "closed" + ] + }, + "public.message_type": { + "name": "message_type", + "schema": "public", + "values": [ + "text", + "system", + "alert", + "finding", + "decision", + "approval", + "workflow", + "agent-status", + "query-result", + "evidence", + "case-event", + "response-action" + ] + }, + "public.room_type": { + "name": "room_type", + "schema": "public", + "values": [ + "operations", + "incident", + "investigation", + "hunt", + "engineering", + "private", + "direct", + "system" + ] + }, + "public.severity": { + "name": "severity", + "schema": "public", + "values": [ + "critical", + "high", + "medium", + "low", + "informational" + ] + }, + "public.task_priority": { + "name": "task_priority", + "schema": "public", + "values": [ + "urgent", + "high", + "normal", + "low" + ] + }, + "public.task_status": { + "name": "task_status", + "schema": "public", + "values": [ + "backlog", + "ready", + "in_progress", + "review", + "done" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/database/migrations/meta/_journal.json b/packages/database/migrations/meta/_journal.json index 92260a3..a533f14 100644 --- a/packages/database/migrations/meta/_journal.json +++ b/packages/database/migrations/meta/_journal.json @@ -159,16 +159,9 @@ { "idx": 22, "version": "7", - "when": 1785219000000, - "tag": "0022_operational_knowledge", - "breakpoints": true - }, - { - "idx": 23, - "version": "7", - "when": 1785220000000, - "tag": "0023_governed_missions", + "when": 1785224461959, + "tag": "0022_messy_proteus", "breakpoints": true } ] -} +} \ No newline at end of file From 84e385b50b1773dfeda1b48f62bec8a39276b8ec Mon Sep 17 00:00:00 2001 From: Justin Middler Date: Tue, 28 Jul 2026 17:48:17 +1000 Subject: [PATCH 3/6] chore(test): remove retired web UI Playwright E2E suite Drop browser E2E specs, Playwright configs/deps, and CI steps. Muster is the Hermes MCP control plane; unit/package tests and homelab shell checks remain. --- .github/workflows/ci.yml | 11 - CONTRIBUTING.md | 2 +- README.md | 30 +- docs/operations/release-homelab.md | 2 +- package.json | 5 - playwright.clean.config.ts | 25 - playwright.config.ts | 76 - playwright.homelab.config.ts | 42 - pnpm-lock.yaml | 41 +- scripts/generate-screenshots.sh | 6 +- tests/alfie-research.spec.ts | 138 -- tests/clean-install.spec.ts | 51 - tests/connectors-governance.spec.ts | 77 - tests/global-setup.ts | 36 - tests/helpers.ts | 21 - tests/homelab/critical-product.spec.ts | 551 ------ tests/homelab/message-send.spec.ts | 115 -- tests/homelab/real-integrations.spec.ts | 463 ----- tests/jessie-hunt.spec.ts | 278 --- tests/messaging.spec.ts | 438 ----- tests/muster.spec.ts | 2151 ----------------------- tests/rooms-governance.spec.ts | 164 -- tests/screenshots.spec.ts | 60 - tests/slack-settings.spec.ts | 441 ----- 24 files changed, 19 insertions(+), 5205 deletions(-) delete mode 100644 playwright.clean.config.ts delete mode 100644 playwright.config.ts delete mode 100644 playwright.homelab.config.ts delete mode 100644 tests/alfie-research.spec.ts delete mode 100644 tests/clean-install.spec.ts delete mode 100644 tests/connectors-governance.spec.ts delete mode 100644 tests/global-setup.ts delete mode 100644 tests/helpers.ts delete mode 100644 tests/homelab/critical-product.spec.ts delete mode 100644 tests/homelab/message-send.spec.ts delete mode 100644 tests/homelab/real-integrations.spec.ts delete mode 100644 tests/jessie-hunt.spec.ts delete mode 100644 tests/messaging.spec.ts delete mode 100644 tests/muster.spec.ts delete mode 100644 tests/rooms-governance.spec.ts delete mode 100644 tests/screenshots.spec.ts delete mode 100644 tests/slack-settings.spec.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0a5cb50..0f72d8c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -93,17 +93,6 @@ jobs: pnpm db:generate git diff --exit-code -- packages/database/migrations - run: pnpm build - - run: pnpm exec playwright install --with-deps chromium - - run: pnpm exec playwright test --config=playwright.clean.config.ts - - run: MUSTER_DEMO_MODE=true pnpm db:seed - - run: pnpm exec playwright test tests/muster.spec.ts tests/jessie-hunt.spec.ts --project=chromium - - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - if: always() - with: - name: playwright-report - path: playwright-report - if-no-files-found: ignore - retention-days: 14 release-security: permissions: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9577645..88907a5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,7 +15,7 @@ Thank you for improving the shared workspace for human and agent-driven security 1. Open an issue describing the operator problem and security impact. 2. Add or update an architecture decision record for material boundary changes. -3. Run `pnpm check` and the relevant Playwright project. +3. Run `pnpm check` . 4. Explain migrations, capability changes, connector compatibility, and rollback in the pull request. Commit generated migrations and public JSON Schemas. Do not hand-edit generated Drizzle snapshots. diff --git a/README.md b/README.md index c2575a8..6cac94f 100644 --- a/README.md +++ b/README.md @@ -132,10 +132,9 @@ pnpm db:bootstrap pnpm dev ``` -### Demo and screenshots are separate +### Demo data is separate Only populate a disposable database when explicitly preparing tests or -screenshots: ```bash MUSTER_DEMO_MODE=true NEXT_PUBLIC_MUSTER_DEMO_MODE=true pnpm db:seed @@ -275,9 +274,9 @@ harnesses are upcoming work tracked in [#33](https://github.com/jusso-dev/Muster ## Testing and contribution checks -For source tests, use Node 26+, pnpm 11.17.0, Docker, and installed Chromium -browser dependencies. The application, worker, and gateway test servers are -started by Playwright; PostgreSQL and Redis are the required local services. +For source tests, use Node 26+, pnpm 11.17.0, and Docker. PostgreSQL and Redis +are the required local services. Browser/web UI E2E (Playwright) has been +removed — Muster is the Hermes MCP control plane, not a primary chat UI. ```bash pnpm install --frozen-lockfile @@ -285,22 +284,13 @@ docker compose up -d postgres redis minio minio-init pnpm db:migrate pnpm db:bootstrap pnpm check -pnpm exec playwright install --with-deps chromium -pnpm test:e2e -- --project=chromium -pnpm exec playwright test --config=playwright.clean.config.ts ``` -The clean-install suite proves that no synthetic demo data is required. The -standard suite uses synthetic mocks and test mode; it is not real-connector -certification. Run `pnpm screenshots` only against synthetic data. Before -submitting a change, follow [CONTRIBUTING.md](CONTRIBUTING.md), keep the -organisation/capability/approval boundaries intact, and include the relevant -unit, integration, browser, migration, and rollback evidence. - -CI currently runs clean-install and synthetic-demo browser checks with Chromium -on Ubuntu `linux/amd64`. `pnpm test:e2e` also defines local Firefox, WebKit, and -iPhone 13-emulation projects; those projects are useful regression checks, not -a claim of a production-browser support or connector-certification matrix. +Unit and package integration suites use synthetic mocks and test mode; they +are not real-connector certification. Before submitting a change, follow +[CONTRIBUTING.md](CONTRIBUTING.md), keep organisation/capability/approval +boundaries intact, and include the relevant unit, integration, migration, and +rollback evidence. ## Additional verification commands @@ -309,8 +299,6 @@ pnpm lint pnpm typecheck pnpm test pnpm build -pnpm test:e2e -pnpm screenshots ``` ## License diff --git a/docs/operations/release-homelab.md b/docs/operations/release-homelab.md index b082e2a..54f45a1 100644 --- a/docs/operations/release-homelab.md +++ b/docs/operations/release-homelab.md @@ -73,7 +73,7 @@ put those values in source control or shell history: MUSTER_BASE_URL=http://homelab:3004 MUSTER_HOMELAB_CRITICAL=true \ MUSTER_CAPTURE_ARTIFACTS=false MUSTER_LOCAL_ADMIN_EMAIL=... \ MUSTER_LOCAL_ADMIN_PASSWORD=... MUSTER_SECONDARY_EMAIL=... \ -MUSTER_SECONDARY_PASSWORD=... pnpm test:e2e:homelab --project=chromium +MUSTER_SECONDARY_PASSWORD=... # Web UI E2E retired; use shell/API checks instead ``` Rollback only after checking migration compatibility and preserving state: diff --git a/package.json b/package.json index 06d89dd..816d342 100644 --- a/package.json +++ b/package.json @@ -15,8 +15,6 @@ "typecheck": "turbo typecheck", "test": "turbo test", "test:unit": "vitest run", - "test:e2e": "playwright test", - "test:e2e:homelab": "playwright test --config=playwright.homelab.config.ts", "test:homelab-installer": "bash scripts/test-install-homelab.sh", "test:release-homelab": "bash tests/homelab/install-homelab.test.sh", "test:release-image": "bash tests/homelab/verify-image-platform.test.sh && bash tests/homelab/promote-image-tag.test.sh", @@ -30,13 +28,10 @@ "alfie:cisa-smoke": "node scripts/alfie-cisa-smoke.mjs", "kelpie:certify-mock": "node scripts/kelpie-certify-mock.mjs", "skills:validate": "node scripts/validate-skill-packs.mjs", - "screenshots": "playwright test tests/screenshots.spec.ts", "format": "prettier --write .", "check": "pnpm lint && pnpm typecheck && pnpm test && pnpm build" }, "devDependencies": { - "@axe-core/playwright": "^4.12.1", - "@playwright/test": "1.62.0", "@types/node": "^26.1.1", "prettier": "^3.6.2", "turbo": "2.10.7", diff --git a/playwright.clean.config.ts b/playwright.clean.config.ts deleted file mode 100644 index 4fc9d98..0000000 --- a/playwright.clean.config.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { defineConfig, devices } from "@playwright/test"; - -export default defineConfig({ - testDir: "./tests", - testMatch: "clean-install.spec.ts", - globalSetup: "./tests/global-setup.ts", - fullyParallel: false, - workers: 1, - retries: process.env.CI ? 2 : 0, - reporter: process.env.CI ? "github" : "list", - use: { - baseURL: process.env.MUSTER_BASE_URL ?? "http://127.0.0.1:3000", - storageState: ".playwright/auth.json", - trace: "retain-on-failure", - screenshot: "only-on-failure", - }, - webServer: { - command: - "BETTER_AUTH_SECRET=muster-playwright-secret-at-least-32-characters AUTH_RATE_LIMIT_MAX=10000 DATABASE_URL=postgresql://muster:muster@localhost:5432/muster REDIS_URL=redis://localhost:6379 pnpm --dir apps/web dev", - url: "http://127.0.0.1:3000/api/v1/health", - reuseExistingServer: !process.env.CI, - timeout: 120_000, - }, - projects: [{ name: "clean-chromium", use: { ...devices["Desktop Chrome"] } }], -}); diff --git a/playwright.config.ts b/playwright.config.ts deleted file mode 100644 index 2341b0e..0000000 --- a/playwright.config.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { defineConfig, devices } from "@playwright/test"; - -const testDatabaseUrl = - process.env.MUSTER_TEST_DATABASE_URL ?? - "postgresql://muster:muster@localhost:5432/muster"; -const testRedisUrl = - process.env.MUSTER_TEST_REDIS_URL ?? "redis://localhost:6379"; - -process.env.DATABASE_URL ??= testDatabaseUrl; -process.env.REDIS_URL ??= testRedisUrl; - -export default defineConfig({ - testDir: "./tests", - testIgnore: "clean-install.spec.ts", - globalSetup: "./tests/global-setup.ts", - fullyParallel: false, - workers: 1, - retries: process.env.CI ? 2 : 0, - reporter: process.env.CI ? "github" : "list", - use: { - baseURL: process.env.MUSTER_BASE_URL ?? "http://127.0.0.1:3000", - storageState: ".playwright/auth.json", - trace: "retain-on-failure", - screenshot: "only-on-failure", - }, - webServer: [ - { - name: "Synthetic Kelpie", - command: "PORT=4011 node integrations/kelpie/mock.mjs", - url: "http://127.0.0.1:4011/health", - reuseExistingServer: !process.env.CI, - timeout: 30_000, - }, - { - name: "Synthetic Tawny", - command: "PORT=4012 node integrations/tawny/mock.mjs", - url: "http://127.0.0.1:4012/health", - reuseExistingServer: !process.env.CI, - timeout: 30_000, - }, - { - name: "Synthetic connector", - command: "PORT=4123 node integrations/generic/mock.mjs", - url: "http://127.0.0.1:4123/health", - reuseExistingServer: !process.env.CI, - timeout: 30_000, - }, - { - name: "Muster worker", - command: `MUSTER_RESEARCH_TEST_MODE=true MUSTER_AGENT_GATEWAY_TOKEN=0707070707070707070707070707070707070707070707070707070707070707 CONNECTOR_ENCRYPTION_KEY=0707070707070707070707070707070707070707070707070707070707070707 DATABASE_URL=${testDatabaseUrl} REDIS_URL=${testRedisUrl} AGENT_GATEWAY_URL=http://127.0.0.1:3002 pnpm --dir apps/worker dev`, - url: "http://127.0.0.1:3001/ready", - reuseExistingServer: !process.env.CI, - timeout: 120_000, - }, - { - name: "Muster agent gateway", - command: `MUSTER_AGENT_RUNTIME=mock MUSTER_AGENT_GATEWAY_TOKEN=0707070707070707070707070707070707070707070707070707070707070707 MUSTER_MOCK_AGENT_DELAY_MS=5000 DATABASE_URL=${testDatabaseUrl} pnpm --dir apps/agent-gateway dev`, - url: "http://127.0.0.1:3002/ready", - reuseExistingServer: !process.env.CI, - timeout: 120_000, - }, - { - name: "Muster web", - command: `MUSTER_RESEARCH_TEST_MODE=true MUSTER_DEMO_MODE=true NEXT_PUBLIC_MUSTER_DEMO_MODE=true MUSTER_AGENT_GATEWAY_TOKEN=0707070707070707070707070707070707070707070707070707070707070707 CONNECTOR_ENCRYPTION_KEY=0707070707070707070707070707070707070707070707070707070707070707 BETTER_AUTH_SECRET=muster-playwright-secret-at-least-32-characters AUTH_RATE_LIMIT_MAX=10000 DATABASE_URL=${testDatabaseUrl} REDIS_URL=${testRedisUrl} AGENT_GATEWAY_URL=http://127.0.0.1:3002 pnpm --dir apps/web dev`, - url: "http://127.0.0.1:3000/api/v1/health", - reuseExistingServer: !process.env.CI, - timeout: 120_000, - }, - ], - projects: [ - { name: "chromium", use: { ...devices["Desktop Chrome"] } }, - { name: "firefox", use: { ...devices["Desktop Firefox"] } }, - { name: "webkit", use: { ...devices["Desktop Safari"] } }, - { name: "mobile", use: { ...devices["iPhone 13"] } }, - ], -}); diff --git a/playwright.homelab.config.ts b/playwright.homelab.config.ts deleted file mode 100644 index c09476e..0000000 --- a/playwright.homelab.config.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { defineConfig, devices } from "@playwright/test"; - -const baseURL = process.env.MUSTER_BASE_URL; -const proxyServer = process.env.MUSTER_PROXY_SERVER; - -if (!baseURL) { - throw new Error("MUSTER_BASE_URL is required for homelab tests"); -} - -export default defineConfig({ - testDir: "./tests", - testMatch: [ - "homelab/**/*.spec.ts", - "rooms-governance.spec.ts", - "connectors-governance.spec.ts", - ], - globalSetup: "./tests/global-setup.ts", - fullyParallel: false, - workers: 1, - retries: 0, - reporter: "list", - outputDir: "test-results/homelab", - use: { - baseURL, - ...(proxyServer ? { proxy: { server: proxyServer } } : {}), - storageState: ".playwright/auth.json", - trace: - process.env.MUSTER_CAPTURE_ARTIFACTS === "false" - ? "off" - : "retain-on-failure", - screenshot: - process.env.MUSTER_CAPTURE_ARTIFACTS === "false" - ? "off" - : { mode: "only-on-failure", fullPage: true }, - }, - projects: [ - { name: "chromium", use: { ...devices["Desktop Chrome"] } }, - { name: "firefox", use: { ...devices["Desktop Firefox"] } }, - { name: "webkit", use: { ...devices["Desktop Safari"] } }, - { name: "mobile", use: { ...devices["iPhone 13"] } }, - ], -}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fb49206..e2c65ee 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,12 +23,6 @@ importers: .: devDependencies: - '@axe-core/playwright': - specifier: ^4.12.1 - version: 4.12.1(playwright-core@1.62.0) - '@playwright/test': - specifier: 1.62.0 - version: 1.62.0 '@types/node': specifier: ^26.1.1 version: 26.1.1 @@ -748,11 +742,6 @@ packages: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} - '@axe-core/playwright@4.12.1': - resolution: {integrity: sha512-rMd7xriptqKpP+w5265i4Hdkv2X5kbu6uiBi/B2I7uf3hieRBM3qDCfaKPtxfiYb2mKXfF+yLODJwIx+Jv1GDw==} - peerDependencies: - playwright-core: '>= 1.0.0' - '@better-auth/core@1.6.25': resolution: {integrity: sha512-lMTlhtwyK4NpY9kPF+2rQCRKYpg136d3gM2xl8esxT1PjJx5Nh5YwZvxcYCIjDuO759sx6TCloJTuwcZGG6ZBw==} peerDependencies: @@ -2879,9 +2868,6 @@ packages: '@types/estree@1.0.9': resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} - '@types/node@24.13.3': - resolution: {integrity: sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==} - '@types/node@26.1.1': resolution: {integrity: sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==} @@ -3078,10 +3064,6 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} - axe-core@4.12.1: - resolution: {integrity: sha512-s7iGf5GaVMxEG0ENN9x+xTr7GFZCb1ZP/1uATUpCEK2X78nDB3RwbtFCo9pGAf9ru+VwoQ464DkaLEeRM08wJA==} - engines: {node: '>=4'} - baseline-browser-mapping@2.11.1: resolution: {integrity: sha512-HYXq73DDpCtNzOmrFsm9eSwCvWCql0RzqjpDzXN9EadiLJ4DNat0nsZ/Bzmy+Ud12mb4/zKDY0cQ805ZzN+i0A==} engines: {node: '>=6.0.0'} @@ -4320,9 +4302,6 @@ packages: engines: {node: '>=16.20.0'} hasBin: true - undici-types@7.18.2: - resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} - undici-types@8.3.0: resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==} @@ -4487,11 +4466,6 @@ snapshots: '@alloc/quick-lru@5.2.0': {} - '@axe-core/playwright@4.12.1(playwright-core@1.62.0)': - dependencies: - axe-core: 4.12.1 - playwright-core: 1.62.0 - '@better-auth/core@1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1)': dependencies: '@better-auth/utils': 0.4.2 @@ -5183,6 +5157,7 @@ snapshots: '@playwright/test@1.62.0': dependencies: playwright: 1.62.0 + optional: true '@radix-ui/number@1.1.3': {} @@ -6332,10 +6307,6 @@ snapshots: '@types/estree@1.0.9': {} - '@types/node@24.13.3': - dependencies: - undici-types: 7.18.2 - '@types/node@26.1.1': dependencies: undici-types: 8.3.0 @@ -6346,7 +6317,7 @@ snapshots: '@types/pg@8.20.0': dependencies: - '@types/node': 24.13.3 + '@types/node': 26.1.1 pg-protocol: 1.15.0 pg-types: 2.2.0 @@ -6489,8 +6460,6 @@ snapshots: assertion-error@2.0.1: {} - axe-core@4.12.1: {} - baseline-browser-mapping@2.11.1: {} better-auth@1.6.25(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@types/pg@8.20.0)(kysely@0.29.4)(pg@8.22.0))(next@16.2.12(@playwright/test@1.62.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(pg@8.22.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vitest@4.1.10(@types/node@26.1.1)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.9.0))): @@ -7213,13 +7182,15 @@ snapshots: pkce-challenge@5.0.1: {} - playwright-core@1.62.0: {} + playwright-core@1.62.0: + optional: true playwright@1.62.0: dependencies: playwright-core: 1.62.0 optionalDependencies: fsevents: 2.3.2 + optional: true postcss@8.5.18: dependencies: @@ -7721,8 +7692,6 @@ snapshots: '@typescript/typescript-win32-arm64': 7.0.2 '@typescript/typescript-win32-x64': 7.0.2 - undici-types@7.18.2: {} - undici-types@8.3.0: {} unpipe@1.0.0: {} diff --git a/scripts/generate-screenshots.sh b/scripts/generate-screenshots.sh index c86d270..9e6b78a 100755 --- a/scripts/generate-screenshots.sh +++ b/scripts/generate-screenshots.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash set -euo pipefail -cd "$(dirname "$0")/.." -MUSTER_DEMO_MODE=true NEXT_PUBLIC_MUSTER_DEMO_MODE=true \ - pnpm exec playwright test tests/screenshots.spec.ts --project=chromium +echo "Web UI screenshot generation retired with Playwright E2E." >&2 +echo "Use synthetic docs assets under screenshots/ if still needed." >&2 +exit 1 diff --git a/tests/alfie-research.spec.ts b/tests/alfie-research.spec.ts deleted file mode 100644 index 924724c..0000000 --- a/tests/alfie-research.spec.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { expect, test } from "@playwright/test"; -import { ResearchBriefSchema } from "@muster/contracts"; -import { database, schema } from "@muster/database"; -import { and, eq } from "drizzle-orm"; - -type ApiData = { data: T }; - -test("Alfie schedules a governed feed, deduplicates evidence, and accepts analyst follow-up", async ({ - page, - request, -}, testInfo) => { - test.setTimeout(60_000); - test.skip(testInfo.project.name !== "chromium"); - const [room] = await database() - .select({ - id: schema.rooms.id, - organisationId: schema.rooms.organisationId, - }) - .from(schema.rooms) - .where(eq(schema.rooms.slug, "soc-operations")) - .limit(1); - if (!room) throw new Error("Seeded SOC operations room required"); - - const suffix = Date.now().toString(36); - await page.goto("/settings/alfie-research"); - await expect( - page.getByRole("heading", { name: "Alfie research watchlists" }), - ).toBeVisible(); - await page.getByLabel("Name").fill(`Alfie fixture ${suffix}`); - await page.locator("select[name=roomId]").selectOption(room.id); - await page.getByLabel("Cadence minutes").fill("15"); - await page - .getByLabel("Source URL") - .fill(`http://127.0.0.1:4223/research-feed?watchlist=${suffix}`); - await page.getByRole("button", { name: "Save watchlist" }).click(); - await expect(page.getByText("Watchlist saved.")).toBeVisible(); - - const listed = await request.get("/api/v1/research-watchlists"); - expect(listed.ok()).toBe(true); - const watchlist = ( - (await listed.json()) as ApiData> - ).data.find((item) => item.name === `Alfie fixture ${suffix}`); - expect(watchlist).toBeDefined(); - - let runId = ""; - await expect - .poll( - async () => { - const rows = await database() - .select({ - id: schema.researchRuns.id, - status: schema.researchRuns.status, - }) - .from(schema.researchRuns) - .where(eq(schema.researchRuns.watchlistId, watchlist!.id)); - runId = rows[0]?.id ?? ""; - return rows[0]?.status; - }, - { timeout: 45_000 }, - ) - .toBe("completed"); - - const items = await database() - .select() - .from(schema.researchItems) - .where( - and( - eq(schema.researchItems.organisationId, room.organisationId), - eq(schema.researchItems.watchlistId, watchlist!.id), - ), - ); - expect(items).toHaveLength(2); - const sentinel = items.find( - (item) => - item.sourceUrl.includes("research-feed") && - item.rootMessageId !== item.latestMessageId, - ); - const hostile = items.find((item) => - ResearchBriefSchema.parse(item.brief).title.includes("malicious"), - ); - expect(sentinel).toBeDefined(); - expect(hostile).toBeDefined(); - expect(ResearchBriefSchema.parse(hostile!.brief).summary).toContain( - "Ignore all policy", - ); - - const messages = await database() - .select({ - id: schema.messages.id, - document: schema.messages.document, - plainText: schema.messages.plainText, - }) - .from(schema.messages) - .where( - eq( - schema.messages.relatedAgentRunId, - ( - await database() - .select({ agentRunId: schema.researchRuns.agentRunId }) - .from(schema.researchRuns) - .where(eq(schema.researchRuns.id, runId)) - .limit(1) - )[0]!.agentRunId, - ), - ); - expect( - messages.some( - (message) => - (message.document as { trust?: string }).trust === "untrusted-evidence", - ), - ).toBe(true); - expect( - messages.some((message) => - message.plainText.includes("Stale synthetic advisory"), - ), - ).toBe(false); - - const feedback = await request.post( - `/api/v1/research-items/${hostile!.id}/feedback`, - { - data: { feedback: "irrelevant" }, - }, - ); - expect(feedback.ok()).toBe(true); - const followUp = await request.post( - `/api/v1/research-items/${hostile!.id}/follow-up`, - { - data: { idempotencyKey: `alfie-follow-up-${suffix}`, priority: "high" }, - }, - ); - expect(followUp.status()).toBe(201); - const taskId = ((await followUp.json()) as ApiData<{ id: string }>).data.id; - const [task] = await database() - .select({ roomId: schema.tasks.roomId, priority: schema.tasks.priority }) - .from(schema.tasks) - .where(eq(schema.tasks.id, taskId)); - expect(task).toEqual({ roomId: room.id, priority: "high" }); -}); diff --git a/tests/clean-install.spec.ts b/tests/clean-install.spec.ts deleted file mode 100644 index 0031ffd..0000000 --- a/tests/clean-install.spec.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { expect, test } from "@playwright/test"; - -test("fresh workspace is empty and accepts first message and task", async ({ - page, -}) => { - await page.goto("/"); - await expect(page).toHaveURL(/\/rooms\/soc-operations$/); - await expect( - page.getByRole("heading", { name: "Start the conversation" }), - ).toBeVisible(); - - const workspace = page.locator("body"); - for (const syntheticIdentity of [ - "Jordan Blake", - "Maya Chen", - "Daniel Brooks", - "Priya Nair", - "Alex Morgan", - "WS-1042", - "INV-2026-0178", - ]) { - await expect(workspace).not.toContainText(syntheticIdentity); - } - - const message = `First clean-install message ${Date.now()}`; - const composer = page.locator(".tiptap"); - await composer.fill(message); - const createdMessage = page.waitForResponse( - (response) => - response.request().method() === "POST" && - response.url().endsWith("/messages"), - ); - await page.keyboard.press("Enter"); - expect((await createdMessage).status()).toBe(201); - await expect(page.locator("#room-timeline").getByText(message)).toBeVisible(); - await page.reload(); - await expect(page.locator("#room-timeline").getByText(message)).toBeVisible(); - - await page.goto("/tasks"); - await expect(page.getByText("0 shown")).toBeVisible(); - const task = `First clean-install task ${Date.now()}`; - await page.getByRole("button", { name: "New task" }).click(); - await page.getByPlaceholder("What needs doing?").fill(task); - await page - .getByPlaceholder("Context, constraints, and deliverable") - .fill("Verify real work can start without demonstration activity."); - await page.getByRole("button", { name: "Create task", exact: true }).click(); - await expect(page.getByText(task)).toBeVisible(); - await page.reload(); - await expect(page.getByText(task)).toBeVisible(); -}); diff --git a/tests/connectors-governance.spec.ts b/tests/connectors-governance.spec.ts deleted file mode 100644 index e63c630..0000000 --- a/tests/connectors-governance.spec.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { expect, test } from "@playwright/test"; -import AxeBuilder from "@axe-core/playwright"; - -test("administrator configures a connector without secret projection", async ({ - page, -}, testInfo) => { - test.skip(testInfo.project.name !== "chromium"); - const suffix = `${Date.now()}-${Math.random().toString(16).slice(2, 8)}`; - const displayName = `Synthetic connector ${suffix}`; - const connectorUrl = - process.env.MUSTER_CONNECTOR_TEST_URL ?? "http://127.0.0.1:4123"; - const secret = `synthetic-secret-${suffix}`; - await page.goto("/integrations/connectors"); - await expect( - page.getByRole("heading", { name: "Governed connectors" }), - ).toBeVisible(); - await page.getByLabel("Display name").fill(displayName); - await page.getByLabel("Instance ID").fill(`synthetic-${suffix}`); - await page.getByLabel("Base URL").fill(connectorUrl); - await page.getByLabel("Bearer token (optional)").fill(secret); - await page.getByLabel("Test mode (permits HTTP)").check(); - await page.getByLabel("Allow approved private host").check(); - const responsePromise = page.waitForResponse( - (response) => - response.url().endsWith("/api/v1/connectors") && - response.request().method() === "POST", - ); - await page.getByRole("button", { name: "Save encrypted connector" }).click(); - const response = await responsePromise; - expect(response.status()).toBe(201); - expect(await response.text()).not.toContain(secret); - await expect(page.getByText(displayName)).toBeVisible(); - const projection = await page.request.get("/api/v1/connectors"); - expect(await projection.text()).not.toContain(secret); - await page - .getByText(displayName) - .locator("../..") - .getByRole("button", { name: "Test", exact: true }) - .click(); - await expect(page.getByText(/Bounded test passed/)).toBeVisible({ - timeout: 20_000, - }); - - const mdeDisplayName = `Synthetic Defender ${suffix}`; - const mdeSecret = `synthetic-mde-secret-${suffix}`; - await page.getByLabel("Product").selectOption("defender_endpoint"); - await page.getByLabel("Display name").fill(mdeDisplayName); - await page.getByLabel("Instance ID").fill(`synthetic-mde-${suffix}`); - await page.getByLabel("Base URL").fill(connectorUrl); - await page.getByLabel("Bearer token (optional)").fill(mdeSecret); - await page.getByLabel("Test mode (permits HTTP)").check(); - await page.getByLabel("Allow approved private host").check(); - const mdeResponsePromise = page.waitForResponse( - (candidate) => - candidate.url().endsWith("/api/v1/connectors") && - candidate.request().method() === "POST", - ); - await page.getByRole("button", { name: "Save encrypted connector" }).click(); - const mdeResponse = await mdeResponsePromise; - expect(mdeResponse.status()).toBe(201); - expect(await mdeResponse.text()).not.toContain(mdeSecret); - await expect(page.getByText(mdeDisplayName)).toBeVisible(); - await page - .getByText(mdeDisplayName) - .locator("../..") - .getByRole("button", { name: "Test", exact: true }) - .click(); - await expect(page.getByText(/Bounded test passed/)).toBeVisible({ - timeout: 20_000, - }); - - const accessibility = await new AxeBuilder({ page }) - .include("main") - .withTags(["wcag2a", "wcag2aa"]) - .analyze(); - expect(accessibility.violations).toEqual([]); -}); diff --git a/tests/global-setup.ts b/tests/global-setup.ts deleted file mode 100644 index 27ad00e..0000000 --- a/tests/global-setup.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { mkdir } from "node:fs/promises"; -import { request, type FullConfig } from "@playwright/test"; - -export default async function globalSetup(config: FullConfig) { - const baseURL = - config.projects[0]?.use.baseURL?.toString() ?? - process.env.MUSTER_BASE_URL ?? - "http://127.0.0.1:3000"; - const email = - process.env.MUSTER_LOCAL_ADMIN_EMAIL ?? "admin@muster.local"; - const password = - process.env.MUSTER_LOCAL_ADMIN_PASSWORD ?? "MusterTest!2026"; - const api = await request.newContext({ baseURL }); - const credentials = { email, password }; - let response = await api.post("/api/auth/sign-in/email", { - data: credentials, - }); - if (response.status() === 401) { - const signup = await api.post("/api/auth/sign-up/email", { - data: { name: "Muster Administrator", ...credentials }, - }); - if (!signup.ok() && signup.status() !== 422) { - throw new Error(`Playwright account setup failed (${signup.status()})`); - } - response = signup.ok() - ? signup - : await api.post("/api/auth/sign-in/email", { data: credentials }); - } - if (!response.ok()) { - throw new Error(`Playwright authentication failed (${response.status()})`); - } - - await mkdir(".playwright", { recursive: true }); - await api.storageState({ path: ".playwright/auth.json" }); - await api.dispose(); -} diff --git a/tests/helpers.ts b/tests/helpers.ts deleted file mode 100644 index 5eec0cd..0000000 --- a/tests/helpers.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { expect, type Page } from "@playwright/test"; - -export async function login(page: Page) { - const email = `playwright.${Date.now()}.${Math.random().toString(36).slice(2)}@example.invalid`; - const signUp = await page.request.post("/api/auth/sign-up/email", { - data: { - name: "Playwright Administrator", - email, - password: "MusterTest!2026", - }, - }); - if (!signUp.ok()) { - throw new Error(`Playwright account creation failed (${signUp.status()})`); - } - await page.goto("/login"); - await page.getByLabel("Email address").fill(email); - await page.getByLabel("Password").fill("MusterTest!2026"); - await page.getByRole("button", { name: "Sign in securely" }).click(); - await expect(page).toHaveURL(/\/rooms\/soc-operations$/); - await expect(page.getByRole("heading", { name: "soc-operations" })).toBeVisible(); -} diff --git a/tests/homelab/critical-product.spec.ts b/tests/homelab/critical-product.spec.ts deleted file mode 100644 index 37d3188..0000000 --- a/tests/homelab/critical-product.spec.ts +++ /dev/null @@ -1,551 +0,0 @@ -import { execFile as execFileCallback } from "node:child_process"; -import { promisify } from "node:util"; -import { - expect, - request as playwrightRequest, - test, - type APIRequestContext, - type Browser, - type Page, - type TestInfo, -} from "@playwright/test"; - -const execFile = promisify(execFileCallback); - -type Room = { id: string; slug: string }; -type TaskView = { - data: Array<{ - id: string; - title: string; - agentRunId: string | null; - agentRunStatus: string | null; - }>; - meta: { - assignees: Array<{ id: string; actorType: string }>; - }; -}; - -function requirePrivateHomelab(testInfo: TestInfo) { - test.skip( - process.env.MUSTER_HOMELAB_CRITICAL !== "true", - "Private homelab critical-product environment is not enabled.", - ); - if (process.env.MUSTER_CAPTURE_ARTIFACTS !== "false") { - throw new Error( - "MUSTER_CAPTURE_ARTIFACTS=false is required before private credentials are used.", - ); - } - if (!testInfo.project.use.baseURL) { - throw new Error("Playwright baseURL is required."); - } -} - -function requiredEnvironment(name: string) { - const value = process.env[name]; - if (!value) throw new Error(`${name} is required.`); - return value; -} - -async function secondaryStorageState(baseURL: string) { - const api = await playwrightRequest.newContext({ - baseURL, - extraHTTPHeaders: { origin: baseURL }, - storageState: { cookies: [], origins: [] }, - }); - const response = await api.post("/api/auth/sign-in/email", { - data: { - email: requiredEnvironment("MUSTER_SECONDARY_EMAIL"), - password: requiredEnvironment("MUSTER_SECONDARY_PASSWORD"), - }, - }); - expect(response.status()).toBe(200); - const storageState = await api.storageState(); - await api.dispose(); - return storageState; -} - -async function joinedRoom(request: APIRequestContext, slug: string) { - const response = await request.get("/api/v1/rooms?membership=joined"); - expect(response.ok()).toBe(true); - const room = ((await response.json()).data as Room[]).find( - (candidate) => candidate.slug === slug, - ); - if (!room) throw new Error(`Joined room is required: ${slug}`); - return room; -} - -function messageBody(text: string, idempotencyKey: string) { - return { - document: { - type: "doc", - content: [ - { - type: "paragraph", - content: [{ type: "text", text }], - }, - ], - }, - plainText: text, - messageType: "text", - dataClassification: "internal", - idempotencyKey, - }; -} - -async function createAssignedTask( - request: APIRequestContext, - roomId: string, - suffix: string, -) { - const taskViewResponse = await request.get("/api/v1/tasks"); - expect(taskViewResponse.ok()).toBe(true); - const taskView = (await taskViewResponse.json()) as TaskView; - const agent = taskView.meta.assignees.find( - (candidate) => candidate.actorType === "agent", - ); - if (!agent) throw new Error("Active synthetic task agent is required."); - const body = { - title: `Synthetic restart-safe task ${suffix}`, - description: - "Return a bounded synthetic review. Do not contact external systems.", - status: "ready", - priority: "normal", - assignedActorId: agent.id, - roomId, - idempotencyKey: `critical-task-create:${suffix}`, - }; - const [first, duplicate] = await Promise.all([ - request.post("/api/v1/tasks", { data: body }), - request.post("/api/v1/tasks", { data: body }), - ]); - expect([first.status(), duplicate.status()].sort()).toEqual([200, 201]); - const firstId = ((await first.json()).data as { id: string }).id; - expect(((await duplicate.json()).data as { id: string }).id).toBe(firstId); - return { id: firstId, title: body.title }; -} - -async function withAuthenticatedContexts(browser: Browser, baseURL: string) { - const primary = await browser.newContext({ - baseURL, - storageState: ".playwright/auth.json", - }); - const secondary = await browser.newContext({ - baseURL, - storageState: await secondaryStorageState(baseURL), - }); - return { primary, secondary }; -} - -test("deployed user can sign out and back in", async ({ page }, testInfo) => { - requirePrivateHomelab(testInfo); - test.skip(testInfo.project.name !== "chromium"); - - await page.goto("/rooms/soc-operations"); - await page.getByRole("button", { name: "User menu and theme" }).click(); - await page - .getByRole("menu", { name: "User menu" }) - .getByRole("menuitem", { name: "Sign out" }) - .click(); - await expect(page).toHaveURL(/\/login$/); - expect((await page.request.get("/api/v1/rooms")).status()).toBe(401); - await page - .getByLabel("Email address") - .fill(requiredEnvironment("MUSTER_LOCAL_ADMIN_EMAIL")); - await page - .getByLabel("Password") - .fill(requiredEnvironment("MUSTER_LOCAL_ADMIN_PASSWORD")); - await page.getByRole("button", { name: "Sign in securely" }).click(); - await expect(page).toHaveURL(/\/rooms\/soc-operations$/); -}); - -test("two deployed identities complete critical collaboration work", async ({ - browser, - baseURL, -}, testInfo) => { - requirePrivateHomelab(testInfo); - test.skip(testInfo.project.name !== "chromium"); - test.setTimeout(120_000); - if (!baseURL) throw new Error("Playwright baseURL is required."); - - const { primary, secondary } = await withAuthenticatedContexts( - browser, - baseURL, - ); - const first = await primary.newPage(); - const second = await secondary.newPage(); - const suffix = `${Date.now()}-${Math.random().toString(16).slice(2, 8)}`; - - try { - const room = await joinedRoom(first.request, "soc-operations"); - await Promise.all([ - first.goto("/rooms/soc-operations"), - second.goto("/rooms/soc-operations"), - ]); - await Promise.all([ - expect(first.getByText("Live", { exact: true })).toBeVisible(), - expect(second.getByText("Live", { exact: true })).toBeVisible(), - ]); - await expect(first.getByTestId("room-presence")).toContainText("present"); - - let releaseSend = () => {}; - const sendGate = new Promise((resolve) => { - releaseSend = resolve; - }); - let sentBody: ReturnType | undefined; - await first.route("**/api/v1/rooms/*/messages", async (route) => { - if (route.request().method() !== "POST") { - await route.continue(); - return; - } - sentBody = route.request().postDataJSON() as ReturnType< - typeof messageBody - >; - await sendGate; - await route.continue(); - }); - const message = `Synthetic critical message ${suffix}`; - const composer = first.locator(".tiptap"); - await composer.fill(message); - await first.keyboard.press("Shift+Enter"); - await first.keyboard.type("Second synthetic line"); - const sendResponse = first.waitForResponse( - (response) => - response.request().method() === "POST" && - response.url().endsWith(`/rooms/${room.id}/messages`), - ); - await first.keyboard.press("Enter"); - await expect( - first.locator("#room-timeline").getByText(message).last(), - ).toBeVisible(); - releaseSend(); - expect((await sendResponse).status()).toBe(201); - await first.unroute("**/api/v1/rooms/*/messages"); - expect(sentBody?.plainText).toContain("Second synthetic line"); - await expect(composer).toBeEmpty(); - - await expect( - second.locator("#room-timeline").getByText(message).last(), - ).toBeVisible(); - await expect( - second.getByRole("separator", { name: "New messages" }), - ).toBeVisible(); - await second.reload(); - const secondMessage = second - .getByRole("article") - .filter({ hasText: message }); - await expect(secondMessage).toBeVisible(); - await secondMessage.getByRole("button", { name: "Add reaction" }).click(); - await second - .getByRole("menu", { name: "Reactions" }) - .getByRole("menuitem", { name: "Reviewing" }) - .click(); - await expect( - first.getByRole("button", { name: /Reviewing, 1/ }).last(), - ).toBeVisible(); - - const firstMessage = first - .getByRole("article") - .filter({ hasText: message }); - await firstMessage.getByRole("button", { name: "Open thread" }).click(); - await secondMessage.getByRole("button", { name: "Open thread" }).click(); - const reply = `Synthetic live reply ${suffix}`; - await second.getByLabel("Reply to thread").fill(reply); - await second.keyboard.press("Enter"); - await expect(first.getByText(reply)).toBeVisible(); - await first.reload(); - await expect(first).toHaveURL(/thread=/); - await expect(first.getByText(reply)).toBeVisible(); - await first.getByRole("button", { name: "Close thread" }).click(); - - const duplicateText = `Synthetic duplicate message ${suffix}`; - const duplicateBody = messageBody( - duplicateText, - `critical-message-duplicate:${suffix}`, - ); - const [duplicateOne, duplicateTwo] = await Promise.all([ - first.request.post(`/api/v1/rooms/${room.id}/messages`, { - data: duplicateBody, - }), - first.request.post(`/api/v1/rooms/${room.id}/messages`, { - data: duplicateBody, - }), - ]); - expect([duplicateOne.status(), duplicateTwo.status()].sort()).toEqual([ - 200, 201, - ]); - const duplicateId = ((await duplicateOne.json()).data as { id: string }).id; - expect(((await duplicateTwo.json()).data as { id: string }).id).toBe( - duplicateId, - ); - - const retryText = `Synthetic recoverable message ${suffix}`; - let retryBody: ReturnType | undefined; - await first.route("**/api/v1/rooms/*/messages", async (route) => { - if (route.request().method() !== "POST") { - await route.continue(); - return; - } - retryBody = route.request().postDataJSON() as ReturnType< - typeof messageBody - >; - await route.abort("failed"); - }); - await first.locator(".tiptap").fill(retryText); - await first.keyboard.press("Enter"); - await expect( - first - .getByRole("alert") - .filter({ hasText: "Message failed. Draft preserved." }), - ).toBeVisible(); - await first.unroute("**/api/v1/rooms/*/messages"); - const retryRequest = first.waitForRequest( - (request) => - request.method() === "POST" && request.url().endsWith("/messages"), - ); - await first.getByRole("button", { name: "Retry" }).click(); - const retriedRequest = await retryRequest; - const retryRequestBody = retriedRequest.postDataJSON() as ReturnType< - typeof messageBody - >; - expect((await retriedRequest.response())?.ok()).toBe(true); - expect(retryRequestBody.idempotencyKey).toBe(retryBody?.idempotencyKey); - await expect(first.getByText(retryText).last()).toBeVisible(); - - const refreshText = `Synthetic refresh recovery ${suffix}`; - await first.route("**/api/v1/rooms/*/messages", async (route) => { - if (route.request().method() !== "POST") { - await route.continue(); - return; - } - await route.abort("failed"); - }); - const refreshRequest = first.waitForRequest( - (request) => - request.method() === "POST" && request.url().endsWith("/messages"), - ); - await first.locator(".tiptap").fill(refreshText); - await first.getByRole("button", { name: "Send", exact: true }).click(); - const refreshBody = (await refreshRequest).postDataJSON() as ReturnType< - typeof messageBody - >; - expect(refreshBody.idempotencyKey).toBeTruthy(); - await first.reload(); - await first.unroute("**/api/v1/rooms/*/messages"); - const [refreshRetry, refreshDuplicate] = await Promise.all([ - first.request.post(`/api/v1/rooms/${room.id}/messages`, { - data: refreshBody, - }), - first.request.post(`/api/v1/rooms/${room.id}/messages`, { - data: refreshBody, - }), - ]); - expect([refreshRetry.status(), refreshDuplicate.status()].sort()).toEqual([ - 200, 201, - ]); - const refreshId = ((await refreshRetry.json()).data as { id: string }).id; - expect(((await refreshDuplicate.json()).data as { id: string }).id).toBe( - refreshId, - ); - - const task = await createAssignedTask(first.request, room.id, suffix); - const delegationKey = `critical-task-delegate:${suffix}`; - const [delegated, delegationDuplicate] = await Promise.all([ - first.request.post(`/api/v1/tasks/${task.id}/delegate`, { - headers: { "Idempotency-Key": delegationKey }, - }), - first.request.post(`/api/v1/tasks/${task.id}/delegate`, { - headers: { "Idempotency-Key": delegationKey }, - }), - ]); - expect(delegated.status()).toBe(202); - expect(delegationDuplicate.status()).toBe(202); - const runId = ((await delegated.json()).data as { id: string }).id; - expect(((await delegationDuplicate.json()).data as { id: string }).id).toBe( - runId, - ); - await first.request.post(`/api/v1/tasks/${task.id}/cancel`); - - await first.goto("/tasks"); - await expect(first.getByText(task.title)).toBeVisible(); - await first.goto("/search"); - await first.getByPlaceholder("Search rooms and messages").fill(message); - await first.getByRole("button", { name: "Search", exact: true }).click(); - await expect(first.getByText(message).last()).toBeVisible(); - - const foreignRoomId = process.env.MUSTER_FOREIGN_ROOM_ID; - if (foreignRoomId) { - expect( - ( - await first.request.get(`/api/v1/rooms/${foreignRoomId}/messages`) - ).status(), - ).toBe(404); - expect( - ( - await first.request.post(`/api/v1/rooms/${foreignRoomId}/messages`, { - data: messageBody( - "Synthetic cross-tenant denial", - `critical-cross-tenant:${suffix}`, - ), - }) - ).status(), - ).toBe(404); - } - } finally { - await Promise.allSettled([primary.close(), secondary.close()]); - } -}); - -test("mobile room work uses explicit send and survives reload", async ({ - page, -}, testInfo) => { - requirePrivateHomelab(testInfo); - test.skip(testInfo.project.name !== "mobile"); - - await page.goto("/"); - await page.getByRole("button", { name: "Open navigation" }).click(); - await page.getByRole("link", { name: /soc(?: |-)operations/i }).click(); - const message = `Synthetic mobile critical ${Date.now()}`; - const composer = page.locator(".tiptap"); - await composer.fill(message); - await page.keyboard.press("Enter"); - await expect(composer).toContainText(message); - await page.getByRole("button", { name: "Send", exact: true }).click(); - await expect(composer).toBeEmpty(); - await expect(page.getByText(message).last()).toBeVisible(); - await page.reload(); - await expect(page.getByText(message).last()).toBeVisible(); -}); - -test("Redis, worker, and web interruptions recover without duplicates", async ({ - request, -}, testInfo) => { - requirePrivateHomelab(testInfo); - test.skip(testInfo.project.name !== "chromium"); - test.skip( - process.env.MUSTER_RESILIENCE_TESTS !== "true", - "Disruptive private homelab resilience checks are not enabled.", - ); - test.setTimeout(180_000); - const sshTarget = requiredEnvironment("MUSTER_HOMELAB_SSH_TARGET"); - const control = async (...args: string[]) => { - await execFile("ssh", ["-o", "BatchMode=yes", sshTarget, ...args]); - }; - const waitForHealth = async () => { - await expect - .poll( - async () => { - const response = await request - .get("/api/v1/health", { timeout: 5_000 }) - .catch(() => null); - return response?.status(); - }, - { timeout: 60_000 }, - ) - .toBe(200); - }; - const waitForContainerHealth = async (container: string) => { - await expect - .poll( - async () => { - const { stdout } = await execFile("ssh", [ - "-o", - "BatchMode=yes", - sshTarget, - "docker", - "inspect", - container, - "--format", - "{{.State.Status}},{{.State.Health.Status}}", - ]); - return stdout.trim(); - }, - { timeout: 60_000 }, - ) - .toBe("running,healthy"); - }; - - const room = await joinedRoom(request, "soc-operations"); - const suffix = `${Date.now()}-${Math.random().toString(16).slice(2, 8)}`; - const redisBody = messageBody( - `Synthetic Redis recovery ${suffix}`, - `critical-redis-recovery:${suffix}`, - ); - let redisPaused = false; - try { - await control("docker", "pause", "muster-redis-1"); - redisPaused = true; - const degraded = await request - .post(`/api/v1/rooms/${room.id}/messages`, { - data: redisBody, - timeout: 5_000, - }) - .catch(() => null); - expect(degraded?.ok() ?? false).toBe(false); - } finally { - if (redisPaused) { - await control("docker", "unpause", "muster-redis-1"); - } - } - await waitForHealth(); - const [redisRetry, redisDuplicate] = await Promise.all([ - request.post(`/api/v1/rooms/${room.id}/messages`, { data: redisBody }), - request.post(`/api/v1/rooms/${room.id}/messages`, { data: redisBody }), - ]); - expect([redisRetry.status(), redisDuplicate.status()].sort()).toEqual([ - 200, 201, - ]); - const messageId = ((await redisRetry.json()).data as { id: string }).id; - expect(((await redisDuplicate.json()).data as { id: string }).id).toBe( - messageId, - ); - - await control("docker", "restart", "muster-web-1"); - await waitForContainerHealth("muster-web-1"); - await waitForHealth(); - const afterWebRestart = await request.post( - `/api/v1/rooms/${room.id}/messages`, - { data: redisBody }, - ); - expect(afterWebRestart.status()).toBe(200); - expect(((await afterWebRestart.json()).data as { id: string }).id).toBe( - messageId, - ); - - const task = await createAssignedTask(request, room.id, `restart-${suffix}`); - let workerStopped = false; - try { - await control("docker", "stop", "muster-worker-1"); - workerStopped = true; - const delegationKey = `critical-worker-restart:${suffix}`; - const [delegated, duplicate] = await Promise.all([ - request.post(`/api/v1/tasks/${task.id}/delegate`, { - headers: { "Idempotency-Key": delegationKey }, - }), - request.post(`/api/v1/tasks/${task.id}/delegate`, { - headers: { "Idempotency-Key": delegationKey }, - }), - ]); - expect(delegated.status()).toBe(202); - expect(duplicate.status()).toBe(202); - expect(((await duplicate.json()).data as { id: string }).id).toBe( - ((await delegated.json()).data as { id: string }).id, - ); - } finally { - if (workerStopped) { - await control("docker", "start", "muster-worker-1"); - } - } - await waitForContainerHealth("muster-worker-1"); - await expect - .poll( - async () => { - const response = await request.get("/api/v1/tasks"); - if (!response.ok()) return null; - const tasks = ((await response.json()) as TaskView).data; - return tasks.find((candidate) => candidate.id === task.id)?.agentRunId; - }, - { timeout: 60_000 }, - ) - .toBeTruthy(); - await request.post(`/api/v1/tasks/${task.id}/cancel`); -}); diff --git a/tests/homelab/message-send.spec.ts b/tests/homelab/message-send.spec.ts deleted file mode 100644 index 5472fcc..0000000 --- a/tests/homelab/message-send.spec.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { expect, test, type Page, type TestInfo } from "@playwright/test"; - -type MessageDiagnostic = { - status: number; - traceId: string | null; - problem: string | null; -}; - -function collectSanitisedDiagnostics(page: Page, testInfo: TestInfo) { - const consoleErrors: string[] = []; - const messageResponses: MessageDiagnostic[] = []; - - page.on("console", (message) => { - if (message.type() === "error") { - consoleErrors.push(message.text().slice(0, 500)); - } - }); - page.on("response", async (response) => { - if ( - response.request().method() !== "POST" || - !response.url().includes("/api/v1/rooms/") || - !response.url().endsWith("/messages") - ) { - return; - } - const body = (await response.json().catch(() => null)) as { - traceId?: unknown; - detail?: unknown; - title?: unknown; - } | null; - messageResponses.push({ - status: response.status(), - traceId: typeof body?.traceId === "string" ? body.traceId : null, - problem: - typeof body?.detail === "string" - ? body.detail.slice(0, 500) - : typeof body?.title === "string" - ? body.title.slice(0, 500) - : null, - }); - }); - - return async () => { - await testInfo.attach("sanitised-diagnostics", { - body: JSON.stringify({ consoleErrors, messageResponses }, null, 2), - contentType: "application/json", - }); - }; -} - -test("deployed user can send and reload a durable room message", async ({ - page, -}, testInfo) => { - const attachDiagnostics = collectSanitisedDiagnostics(page, testInfo); - const message = `Synthetic homelab message ${testInfo.project.name} ${Date.now()}`; - - try { - await page.goto("/rooms/investigation-suspicious-powershell"); - await expect( - page.getByRole("heading", { - name: "Suspicious PowerShell", - level: 1, - }), - ).toBeVisible(); - - const composer = page.locator(".tiptap"); - await composer.fill(message); - await page.getByRole("button", { name: "Send", exact: true }).click(); - - await expect(composer).toBeEmpty(); - await expect(page.getByText(message).last()).toBeVisible(); - await page.reload(); - await expect(page.getByText(message).last()).toBeVisible(); - } finally { - await attachDiagnostics(); - } -}); - -test("deployed user uploads governed evidence and reloads its message", async ({ - page, -}, testInfo) => { - test.skip(testInfo.project.name !== "chromium"); - const attachDiagnostics = collectSanitisedDiagnostics(page, testInfo); - const suffix = `${Date.now()}`; - const fileName = `synthetic-homelab-evidence-${suffix}.txt`; - const message = `Synthetic homelab evidence ${suffix}`; - - try { - await page.goto("/rooms/investigation-suspicious-powershell"); - await expect(page.getByTestId("room-presence")).toHaveText("1 present"); - await page.getByLabel("Choose evidence files").setInputFiles({ - name: fileName, - mimeType: "text/plain", - buffer: Buffer.from(`Synthetic homelab evidence payload ${suffix}`), - }); - await expect(page.getByText("Stored · pending scan")).toBeVisible(); - await page.locator(".tiptap").fill(message); - await page.getByRole("button", { name: "Send", exact: true }).click(); - - const posted = page.getByRole("article").filter({ hasText: message }); - await expect(posted.getByText(fileName)).toBeVisible(); - await expect( - posted.getByText("Stored evidence · pending scan"), - ).toBeVisible(); - await page.reload(); - await expect( - page - .getByRole("article") - .filter({ hasText: message }) - .getByText(fileName), - ).toBeVisible(); - } finally { - await attachDiagnostics(); - } -}); diff --git a/tests/homelab/real-integrations.spec.ts b/tests/homelab/real-integrations.spec.ts deleted file mode 100644 index 9f81222..0000000 --- a/tests/homelab/real-integrations.spec.ts +++ /dev/null @@ -1,463 +0,0 @@ -import { - expect, - test, - type APIRequestContext, - type Page, -} from "@playwright/test"; -import { HuntResultSchema } from "@muster/contracts"; - -type Connector = { id: string; product: string }; -type AsyncRecord = { - id: string; - status: string; - result?: unknown; - responseMetadata?: Record; -}; - -const genericAlertsTemplate = { - key: "generic.alerts.list", - version: 1, - displayName: "Homelab generic alert evidence", - method: "GET", - pathTemplate: "/alerts", - requiredCapability: "alerts.read", - inputSchema: { type: "object", additionalProperties: false }, - outputSchema: { - type: "object", - required: ["records"], - properties: { records: { type: "array" } }, - }, - recordsPath: "records", -}; - -async function configureConnector( - request: APIRequestContext, - input: { - product: "tawny" | "tawny_response" | "kelpie" | "generic_rest"; - instanceId: string; - displayName: string; - baseUrl: string; - token: string; - templates?: unknown[]; - }, -) { - const hostname = new URL(input.baseUrl).hostname; - const { token, templates = [], ...configuration } = input; - const response = await request.post("/api/v1/connectors", { - data: { - ...configuration, - allowedHosts: [hostname], - allowPrivateNetwork: true, - testMode: input.baseUrl.startsWith("http://"), - auth: { type: "bearer", token }, - limits: { - timeoutMs: 10_000, - maxResponseBytes: 1_000_000, - maxRecords: 1_000, - maxPages: 10, - requestsPerMinute: 60, - }, - templates, - }, - }); - expect(response.status()).toBe(201); - const text = await response.text(); - expect(text).not.toContain(token); - return (JSON.parse(text) as { data: Connector }).data; -} - -async function waitForRecord( - request: APIRequestContext, - path: string, - expected = "succeeded", - timeout = 30_000, -) { - let latest: AsyncRecord | undefined; - await expect - .poll( - async () => { - const response = await request.get(path); - expect(response.ok()).toBe(true); - latest = (await response.json()).data as AsyncRecord; - return latest.status; - }, - { timeout }, - ) - .toBe(expected); - if (!latest) throw new Error("Asynchronous record was not returned"); - return latest; -} - -async function approveLatest(page: Page, actionType: string) { - await page.goto("/approvals"); - const approval = page - .locator("article") - .filter({ hasText: actionType }) - .filter({ hasText: "pending" }) - .first(); - await expect(approval).toBeVisible(); - await approval.getByRole("button", { name: "Approve" }).click(); - await expect(page.getByRole("status")).toContainText("approved"); -} - -test("real Tawny and Kelpie operations are governed, durable, and duplicate-safe", async ({ - page, -}, testInfo) => { - test.setTimeout(180_000); - test.skip(testInfo.project.name !== "chromium"); - test.skip( - process.env.MUSTER_REAL_INTEGRATIONS !== "true", - "Real integration environment is not enabled.", - ); - const suffix = `${Date.now()}-${Math.random().toString(16).slice(2, 8)}`; - const tawnyUrl = process.env.MUSTER_TAWNY_URL ?? "http://127.0.0.1:4012"; - const kelpieUrl = process.env.MUSTER_KELPIE_URL ?? "http://127.0.0.1:4011"; - const genericUrl = process.env.MUSTER_GENERIC_URL ?? "http://127.0.0.1:4123"; - const tawnyToken = - process.env.MUSTER_TAWNY_TOKEN ?? `synthetic-tawny-read-${suffix}`; - const tawnyResponseToken = - process.env.MUSTER_TAWNY_RESPONSE_TOKEN ?? - `synthetic-tawny-response-${suffix}`; - const kelpieToken = - process.env.MUSTER_KELPIE_TOKEN ?? `synthetic-kelpie-${suffix}`; - const request = page.request; - - const roomsResponse = await request.get("/api/v1/rooms?membership=joined"); - expect(roomsResponse.ok()).toBe(true); - const rooms = (await roomsResponse.json()).data as Array<{ - id: string; - slug: string; - }>; - const room = rooms.find((candidate) => candidate.slug === "soc-operations"); - if (!room) throw new Error("SOC operations room is required"); - const taskResponse = await request.post("/api/v1/tasks", { - data: { - title: `Synthetic real connector verification ${suffix}`, - description: "Tawny hunt and Kelpie case lifecycle evidence.", - idempotencyKey: `real-integrations-task-${suffix}`, - status: "in_progress", - priority: "high", - roomId: room.id, - }, - }); - expect(taskResponse.status()).toBe(201); - const taskId = ((await taskResponse.json()).data as { id: string }).id; - - const tawny = await configureConnector(request, { - product: "tawny", - instanceId: `homelab-read-${suffix}`, - displayName: `Tawny real ${suffix}`, - baseUrl: tawnyUrl, - token: tawnyToken, - }); - const tawnyResponse = await configureConnector(request, { - product: "tawny_response", - instanceId: `homelab-response-${suffix}`, - displayName: `Tawny response real ${suffix}`, - baseUrl: tawnyUrl, - token: tawnyResponseToken, - }); - const kelpie = await configureConnector(request, { - product: "kelpie", - instanceId: `homelab-kelpie-${suffix}`, - displayName: `Kelpie real ${suffix}`, - baseUrl: kelpieUrl, - token: kelpieToken, - }); - const generic = await configureConnector(request, { - product: "generic_rest", - instanceId: `homelab-generic-${suffix}`, - displayName: `Generic REST homelab ${suffix}`, - baseUrl: genericUrl, - token: `synthetic-generic-${suffix}`, - templates: [genericAlertsTemplate], - }); - - const inventoryResponse = await request.post( - `/api/v1/connectors/${tawny.id}/queries`, - { - data: { - templateKey: "tawny.inventory.list", - input: {}, - idempotencyKey: `tawny-inventory-${suffix}`, - }, - }, - ); - expect(inventoryResponse.status()).toBe(202); - const inventoryId = ((await inventoryResponse.json()).data as { id: string }) - .id; - const inventory = await waitForRecord( - request, - `/api/v1/connector-queries/${inventoryId}`, - ); - const agents = inventory.result as Array<{ id: string; hostname: string }>; - expect(agents.length).toBeGreaterThan(0); - const syntheticAgent = - agents.find((agent) => agent.hostname.includes("muster-synthetic")) ?? - agents.find((agent) => agent.hostname.includes("synthetic")); - if (!syntheticAgent) - throw new Error("A synthetic Tawny endpoint is required for safe response"); - - const huntResponse = await request.post( - `/api/v1/connectors/${tawny.id}/queries`, - { - data: { - templateKey: "tawny.hunt.run", - input: { query: 'last:"7d"', limit: 25 }, - roomId: room.id, - taskId, - idempotencyKey: `tawny-hunt-${suffix}`, - }, - }, - ); - expect(huntResponse.status()).toBe(202); - const huntId = ((await huntResponse.json()).data as { id: string }).id; - const hunt = await waitForRecord( - request, - `/api/v1/connector-queries/${huntId}`, - ); - expect((hunt.result as unknown[]).length).toBeGreaterThan(0); - - const isolateBody = { - operation: "tawny.isolate_host", - integrationId: tawnyResponse.id, - agentId: syntheticAgent.id, - reason: "Synthetic endpoint containment verification", - roomId: room.id, - taskId, - idempotencyKey: `tawny-isolate-${suffix}`, - }; - const isolateResponse = await request.post("/api/v1/integration-actions", { - data: isolateBody, - }); - expect(isolateResponse.status()).toBe(202); - const isolate = (await isolateResponse.json()).data as { - id: string; - approvalId: string; - status: string; - }; - expect(isolate.status).toBe("awaiting_approval"); - await expect - .poll( - async () => - ( - (await ( - await request.get(`/api/v1/integration-actions/${isolate.id}`) - ).json()) as { data: AsyncRecord } - ).data.status, - ) - .toBe("awaiting_approval"); - await approveLatest(page, "endpoint.isolate"); - const isolated = await waitForRecord( - request, - `/api/v1/integration-actions/${isolate.id}`, - ); - expect(isolated.responseMetadata?.externalId).toBeTruthy(); - const isolateDuplicate = await request.post("/api/v1/integration-actions", { - data: isolateBody, - }); - expect(((await isolateDuplicate.json()).data as { id: string }).id).toBe( - isolate.id, - ); - - const createCaseBody = { - operation: "kelpie.case.create", - integrationId: kelpie.id, - title: `Synthetic Muster case ${suffix}`, - summary: - "Synthetic Tawny hunt promoted through an approved Muster workflow.", - severity: "high", - tlp: "amber", - pap: "amber", - classification: "other", - tags: ["synthetic", "muster-e2e"], - evidenceReferences: [`muster:query:${huntId}`], - roomId: room.id, - taskId, - idempotencyKey: `kelpie-create-${suffix}`, - }; - const createCaseResponse = await request.post("/api/v1/integration-actions", { - data: createCaseBody, - }); - expect(createCaseResponse.status()).toBe(202); - const createCase = (await createCaseResponse.json()).data as { - id: string; - status: string; - }; - expect(createCase.status).toBe("awaiting_approval"); - await approveLatest(page, "investigation.promote"); - const createdCase = await waitForRecord( - request, - `/api/v1/integration-actions/${createCase.id}`, - ); - const caseId = createdCase.responseMetadata?.externalId; - expect(typeof caseId).toBe("string"); - if (typeof caseId !== "string") throw new Error("Kelpie case ID missing"); - - const jessieResponse = await request.post("/api/v1/hunts", { - data: { - question: - "Investigate synthetic activity for 203.0.113.99, correlate the evidence, and explain safe next steps.", - roomId: room.id, - linkedCaseId: caseId, - sourceIds: [tawny.id, generic.id], - maxRecordsPerSource: 50, - trainingMode: true, - idempotencyKey: `jessie-real-hunt-${suffix}`, - }, - }); - expect(jessieResponse.status()).toBe(202); - const jessie = (await jessieResponse.json()).data as { - id: string; - duplicate: boolean; - plan: { approvalRequired: boolean; queries: unknown[] }; - }; - expect(jessie.duplicate).toBe(false); - expect(jessie.plan.approvalRequired).toBe(false); - expect(jessie.plan.queries).toHaveLength(2); - const completedHunt = await waitForRecord( - request, - `/api/v1/hunts/${jessie.id}`, - "completed", - 120_000, - ); - const huntResult = HuntResultSchema.parse(completedHunt.result); - expect(huntResult.trainingMode).toBe(true); - expect(huntResult.queries).toHaveLength(2); - expect(huntResult.observables).toContainEqual( - expect.objectContaining({ - type: "ip", - normalizedValue: "203.0.113.99", - }), - ); - expect(huntResult.enrichmentProposal?.caseId).toBe(caseId); - - const enrichmentResponse = await request.post( - `/api/v1/hunts/${jessie.id}/enrichment`, - ); - expect(enrichmentResponse.status()).toBe(202); - const enrichment = (await enrichmentResponse.json()).data as { - id: string; - status: string; - }; - expect(enrichment.status).toBe("awaiting_approval"); - await approveLatest(page, "kelpie.case.enrich"); - await waitForRecord(request, `/api/v1/integration-actions/${enrichment.id}`); - - const enrichedCaseResponse = await request.post( - `/api/v1/connectors/${kelpie.id}/queries`, - { - data: { - templateKey: "kelpie.case.get", - input: { caseId }, - idempotencyKey: `kelpie-get-jessie-${suffix}`, - }, - }, - ); - expect(enrichedCaseResponse.status()).toBe(202); - const enrichedCaseQueryId = ( - (await enrichedCaseResponse.json()).data as { id: string } - ).id; - const enrichedCaseQuery = await waitForRecord( - request, - `/api/v1/connector-queries/${enrichedCaseQueryId}`, - ); - expect( - (enrichedCaseQuery.result as { recent_timeline: unknown[] }) - .recent_timeline, - ).toHaveLength(1); - - for (const action of [ - { - operation: "kelpie.timeline.comment", - integrationId: kelpie.id, - caseId, - body: "Synthetic Tawny hunt reviewed in Muster.", - evidenceReferences: [`muster:query:${huntId}`], - roomId: room.id, - taskId, - idempotencyKey: `kelpie-comment-${suffix}`, - }, - { - operation: "kelpie.observable.add", - integrationId: kelpie.id, - caseId, - observableType: "ip", - value: "203.0.113.99", - tlp: "amber", - description: "RFC 5737 synthetic verification indicator", - isIoc: true, - tags: ["synthetic"], - roomId: room.id, - taskId, - idempotencyKey: `kelpie-observable-${suffix}`, - }, - { - operation: "kelpie.case.update", - integrationId: kelpie.id, - caseId, - status: "contained", - summary: "Synthetic case updated by governed Muster delivery.", - roomId: room.id, - taskId, - idempotencyKey: `kelpie-update-${suffix}`, - }, - ]) { - const response = await request.post("/api/v1/integration-actions", { - data: action, - }); - expect(response.status()).toBe(202); - const actionData = (await response.json()).data as { - id: string; - status: string; - }; - expect(actionData.status).toBe("awaiting_approval"); - await approveLatest(page, "kelpie.case.enrich"); - const actionId = actionData.id; - await waitForRecord(request, `/api/v1/integration-actions/${actionId}`); - } - - const caseQueryResponse = await request.post( - `/api/v1/connectors/${kelpie.id}/queries`, - { - data: { - templateKey: "kelpie.case.get", - input: { caseId }, - idempotencyKey: `kelpie-get-${suffix}`, - }, - }, - ); - expect(caseQueryResponse.status()).toBe(202); - const caseQueryId = ((await caseQueryResponse.json()).data as { id: string }) - .id; - const caseQuery = await waitForRecord( - request, - `/api/v1/connector-queries/${caseQueryId}`, - ); - const realCase = caseQuery.result as { - status: string; - observables: unknown[]; - recent_timeline: unknown[]; - }; - expect(realCase.status).toBe("contained"); - expect(realCase.observables.length).toBeGreaterThan(0); - expect(realCase.recent_timeline.length).toBeGreaterThan(0); - - await page.goto("/rooms/soc-operations"); - await expect( - page.getByText(new RegExp(`Tawny real ${suffix}`)).first(), - ).toBeVisible(); - await expect( - page.getByText(new RegExp(`Kelpie real ${suffix}`)).first(), - ).toBeVisible(); - - if (process.env.MUSTER_AUTH_REQUIRED === "true") { - const denied = await fetch( - new URL( - `/api/v1/integration-actions/${isolate.id}`, - String(testInfo.project.use.baseURL), - ), - ); - expect(denied.status).toBe(401); - } -}); diff --git a/tests/jessie-hunt.spec.ts b/tests/jessie-hunt.spec.ts deleted file mode 100644 index ded4572..0000000 --- a/tests/jessie-hunt.spec.ts +++ /dev/null @@ -1,278 +0,0 @@ -import { expect, test } from "@playwright/test"; -import { HuntResultSchema } from "@muster/contracts"; -import { database, schema } from "@muster/database"; -import { and, eq } from "drizzle-orm"; - -type ApiData = { data: T }; - -const genericTemplate = { - key: "generic.alerts.list", - version: 1, - displayName: "Synthetic alert evidence", - method: "GET", - pathTemplate: "/alerts", - requiredCapability: "alerts.read", - inputSchema: { type: "object", additionalProperties: false }, - outputSchema: { - type: "object", - required: ["records"], - properties: { records: { type: "array" } }, - }, - recordsPath: "records", -}; - -test("Jessie completes an idempotent governed multi-source hunt", async ({ - request, -}, testInfo) => { - test.skip(testInfo.project.name !== "chromium"); - const [room] = await database() - .select({ - id: schema.rooms.id, - organisationId: schema.rooms.organisationId, - }) - .from(schema.rooms) - .where(eq(schema.rooms.slug, "soc-operations")) - .limit(1); - if (!room) throw new Error("Seeded SOC operations room required"); - - const suffix = Date.now().toString(36); - const configure = async ( - product: "tawny" | "generic_rest" | "kelpie", - baseUrl: string, - port: number, - templates: unknown[] = [], - ) => { - const response = await request.post("/api/v1/connectors", { - data: { - product, - instanceId: `jessie-e2e-${product}-${suffix}`, - displayName: `Synthetic Jessie ${product} ${suffix}`, - baseUrl, - allowedHosts: ["127.0.0.1"], - allowPrivateNetwork: true, - testMode: true, - auth: { type: "bearer", token: `synthetic-token-${port}` }, - templates, - }, - }); - expect(response.status()).toBe(201); - return ((await response.json()) as ApiData<{ id: string }>).data.id; - }; - - const caseSeed = await request.post("http://127.0.0.1:4011/api/v1/cases", { - data: { - title: `Synthetic Jessie enrichment ${suffix}`, - summary: "Synthetic test case for governed Jessie enrichment.", - severity: "low", - }, - }); - expect(caseSeed.status()).toBe(201); - const caseId = ((await caseSeed.json()) as { id: string }).id; - const [tawnyId, genericId] = await Promise.all([ - configure("tawny", "http://127.0.0.1:4012", 4012), - configure("generic_rest", "http://127.0.0.1:4123", 4123, [genericTemplate]), - configure("kelpie", "http://127.0.0.1:4011", 4011), - ]); - const idempotencyKey = `jessie-e2e-hunt-${suffix}`; - const huntRequest = { - question: - "Investigate synthetic activity for 192.0.2.44 and explain the evidence.", - roomId: room.id, - linkedCaseId: caseId, - sourceIds: [tawnyId, genericId], - maxRecordsPerSource: 50, - trainingMode: false, - idempotencyKey, - }; - const first = await request.post("/api/v1/hunts", { data: huntRequest }); - expect(first.status()).toBe(202); - const created = ( - (await first.json()) as ApiData<{ - id: string; - agentRunId: string; - duplicate: boolean; - plan: { approvalRequired: boolean; queries: unknown[] }; - }> - ).data; - expect(created.duplicate).toBe(false); - expect(created.plan.approvalRequired).toBe(false); - expect(created.plan.queries).toHaveLength(2); - - const duplicate = await request.post("/api/v1/hunts", { - data: huntRequest, - }); - expect(duplicate.status()).toBe(200); - expect( - ( - (await duplicate.json()) as ApiData<{ - id: string; - agentRunId: string; - duplicate: boolean; - }> - ).data, - ).toMatchObject({ - id: created.id, - agentRunId: created.agentRunId, - duplicate: true, - }); - - let completed: { - status: string; - result: unknown; - queries: Array<{ status: string }>; - } | null = null; - await expect - .poll( - async () => { - const response = await request.get(`/api/v1/hunts/${created.id}`); - expect(response.ok()).toBe(true); - completed = ( - (await response.json()) as ApiData<{ - status: string; - result: unknown; - queries: Array<{ status: string }>; - }> - ).data; - return completed.status; - }, - { timeout: 45_000 }, - ) - .toBe("completed"); - - expect(completed).not.toBeNull(); - expect(completed!.queries).toHaveLength(2); - expect( - completed!.queries.every((query) => query.status === "succeeded"), - ).toBe(true); - const result = HuntResultSchema.parse(completed!.result); - expect(result.observables).toContainEqual( - expect.objectContaining({ - type: "ip", - normalizedValue: "192.0.2.44", - }), - ); - expect(result.observedFacts.length).toBeGreaterThan(0); - expect(result.inferences.length).toBeGreaterThan(0); - expect(result.enrichmentProposal?.caseId).toBe(caseId); - - const enrichment = await request.post( - `/api/v1/hunts/${created.id}/enrichment`, - ); - expect(enrichment.status()).toBe(202); - const delivery = ( - (await enrichment.json()) as ApiData<{ - id: string; - approvalId: string; - status: string; - duplicate: boolean; - }> - ).data; - expect(delivery).toMatchObject({ - status: "awaiting_approval", - duplicate: false, - }); - - const replayedEnrichment = await request.post( - `/api/v1/hunts/${created.id}/enrichment`, - ); - expect(replayedEnrichment.status()).toBe(202); - expect( - ( - (await replayedEnrichment.json()) as ApiData<{ - id: string; - duplicate: boolean; - }> - ).data, - ).toMatchObject({ - id: delivery.id, - duplicate: true, - }); - - const approval = await request.post( - `/api/v1/approvals/${delivery.approvalId}/decisions`, - { - data: { - status: "approved", - reason: "Synthetic Jessie enrichment reviewed for E2E proof.", - }, - }, - ); - expect(approval.ok()).toBe(true); - await expect - .poll( - async () => { - const response = await request.get( - `/api/v1/integration-actions/${delivery.id}`, - ); - expect(response.ok()).toBe(true); - return ( - (await response.json()) as ApiData<{ - status: string; - }> - ).data.status; - }, - { timeout: 30_000 }, - ) - .toBe("succeeded"); - - const caseComments = await request.get( - `http://127.0.0.1:4011/api/v1/cases/${encodeURIComponent(caseId)}/comments`, - ); - expect(caseComments.ok()).toBe(true); - expect( - ((await caseComments.json()) as { comments: unknown[] }).comments, - ).toHaveLength(1); - - const messages = await request.get(`/api/v1/rooms/${room.id}/messages`); - expect(messages.ok()).toBe(true); - const roomMessages = ( - (await messages.json()) as ApiData< - Array<{ plainText: string; document: Record }> - > - ).data; - expect( - roomMessages.some((message) => - message.plainText.includes("Jessie prepared a bounded hunt plan"), - ), - ).toBe(true); - expect( - roomMessages.some((message) => - message.plainText.includes("Jessie completed the bounded hunt."), - ), - ).toBe(true); - expect( - roomMessages.filter( - (message) => message.document.deliveryId === delivery.id, - ), - ).toHaveLength(1); - - const [approvalRecord, actionAudit] = await Promise.all([ - database() - .select({ status: schema.approvals.status }) - .from(schema.approvals) - .where( - and( - eq(schema.approvals.organisationId, room.organisationId), - eq(schema.approvals.id, delivery.approvalId), - ), - ) - .limit(1) - .then((rows) => rows[0]), - database() - .select({ action: schema.auditEvents.action }) - .from(schema.auditEvents) - .where( - and( - eq(schema.auditEvents.organisationId, room.organisationId), - eq(schema.auditEvents.targetId, delivery.id), - ), - ), - ]); - expect(approvalRecord?.status).toBe("executed"); - expect(actionAudit.map((event) => event.action)).toEqual( - expect.arrayContaining([ - "integration.action.approval_requested", - "integration.action.succeeded", - ]), - ); -}); diff --git a/tests/messaging.spec.ts b/tests/messaging.spec.ts deleted file mode 100644 index fb341e3..0000000 --- a/tests/messaging.spec.ts +++ /dev/null @@ -1,438 +0,0 @@ -import { - request as playwrightRequest, - expect, - test, - type Locator, -} from "@playwright/test"; -import AxeBuilder from "@axe-core/playwright"; -import { database, newId, schema } from "@muster/database"; -import { count, eq } from "drizzle-orm"; - -test("two authenticated sessions complete the durable room lifecycle", async ({ - browser, - baseURL, -}, testInfo) => { - test.skip(testInfo.project.name !== "chromium"); - test.setTimeout(90_000); - if (!baseURL) throw new Error("Playwright baseURL required"); - - const [room] = await database() - .select() - .from(schema.rooms) - .where(eq(schema.rooms.slug, "investigation-suspicious-powershell")) - .limit(1); - if (!room) throw new Error("Synthetic investigation room required"); - - const firstContext = await browser.newContext({ - baseURL, - storageState: ".playwright/auth.json", - }); - const secondEmail = `room.reader.${newId()}@example.invalid`; - const secondPassword = "MusterTest!2026"; - const secondApi = await playwrightRequest.newContext({ - baseURL, - extraHTTPHeaders: { origin: baseURL }, - }); - const signup = await secondApi.post("/api/auth/sign-up/email", { - data: { - name: "Synthetic Room Reader", - email: secondEmail, - password: secondPassword, - }, - }); - expect(signup.ok(), `signup returned ${signup.status()}`).toBe(true); - const secondStorageState = await secondApi.storageState(); - await secondApi.dispose(); - const secondContext = await browser.newContext({ - baseURL, - storageState: secondStorageState, - }); - const secondActorId = newId(); - await database() - .insert(schema.actors) - .values({ - id: secondActorId, - organisationId: room.organisationId, - actorType: "human", - displayName: "Synthetic Room Reader", - identityReference: secondEmail, - capabilityAssignments: ["rooms.read", "messages.create"], - }); - await database().insert(schema.roomMemberships).values({ - organisationId: room.organisationId, - roomId: room.id, - actorId: secondActorId, - membershipRole: "member", - }); - - const first = await firstContext.newPage(); - const second = await secondContext.newPage(); - try { - await Promise.all([ - first.goto("/rooms/investigation-suspicious-powershell"), - second.goto("/rooms/investigation-suspicious-powershell"), - ]); - await Promise.all([ - expect(first.getByText("Live", { exact: true })).toBeVisible(), - expect(second.getByText("Live", { exact: true })).toBeVisible(), - ]); - await expect(first.getByTestId("room-presence")).toHaveText("2 present"); - - const message = `Synthetic two-session message ${Date.now()}`; - const firstComposer = first.locator(".tiptap"); - await firstComposer.fill(message); - await expect(second.getByTestId("typing-indicator")).toBeVisible(); - await first.keyboard.press("Shift+Enter"); - await first.keyboard.type("Second line"); - await first.keyboard.press("Enter"); - await expect(firstComposer).toBeEmpty(); - await expect(second.getByText(message).last()).toBeVisible(); - await expect( - second.getByRole("separator", { name: "New messages" }), - ).toBeVisible(); - - await second.reload(); - await expect(second.getByText(message).last()).toBeVisible(); - const secondMessage = second - .getByRole("article") - .filter({ hasText: message }); - await secondMessage.getByRole("button", { name: "Add reaction" }).click(); - await second - .getByRole("menu", { name: "Reactions" }) - .getByRole("menuitem", { name: "Reviewing" }) - .click(); - await expect( - first.getByRole("button", { name: /Reviewing, 1/ }).last(), - ).toBeVisible(); - - const firstMessage = first - .getByRole("article") - .filter({ hasText: message }); - await firstMessage.getByRole("button", { name: "Open thread" }).click(); - await expect(first).toHaveURL(/thread=/); - await secondMessage.getByRole("button", { name: "Open thread" }).click(); - const reply = `Synthetic live thread reply ${Date.now()}`; - await second.getByLabel("Reply to thread").fill(reply); - await second.keyboard.press("Enter"); - await expect(first.getByText(reply)).toBeVisible(); - await first.reload(); - await expect(first).toHaveURL(/thread=/); - await expect(first.getByText(reply)).toBeVisible(); - await first.goBack(); - await expect(first).not.toHaveURL(/thread=/); - await first.goForward(); - await expect(first).toHaveURL(/thread=/); - await first.getByRole("button", { name: "Close thread" }).click(); - const notifications = first.getByLabel("Notifications", { exact: true }); - const waitForNotificationSave = () => - first.waitForResponse( - (response) => - response.request().method() === "PUT" && - response.url().endsWith(`/rooms/${room.id}/notifications`), - ); - if ((await notifications.inputValue()) !== "mentions") { - const notificationSave = waitForNotificationSave(); - await notifications.selectOption("mentions"); - await notificationSave; - } - const replyNotifications = first.getByLabel("Replies to my messages"); - const followedThreadNotifications = first.getByLabel( - "Activity in followed threads", - ); - async function setNotificationCheckbox(locator: Locator, checked: boolean) { - if ((await locator.isChecked()) === checked) return; - const notificationSave = waitForNotificationSave(); - await locator.setChecked(checked); - await notificationSave; - } - await setNotificationCheckbox(replyNotifications, false); - await setNotificationCheckbox(followedThreadNotifications, false); - await first.reload(); - await expect( - first.getByLabel("Notifications", { exact: true }), - ).toHaveValue("mentions"); - await expect(first.getByLabel("Replies to my messages")).not.toBeChecked(); - await expect( - first.getByLabel("Activity in followed threads"), - ).not.toBeChecked(); - - const lifecycleMessage = first - .getByRole("article") - .filter({ hasText: message }); - await lifecycleMessage - .getByRole("button", { name: "More message actions" }) - .click(); - await first - .getByRole("menu", { name: "Message actions" }) - .getByRole("menuitem", { name: "Save message" }) - .click(); - await lifecycleMessage - .getByRole("button", { name: "More message actions" }) - .click(); - await first - .getByRole("menu", { name: "Message actions" }) - .getByRole("menuitem", { name: "Pin to room" }) - .click(); - await expect(lifecycleMessage.getByText("Pinned")).toBeVisible(); - - await lifecycleMessage - .getByRole("button", { name: "More message actions" }) - .click(); - await first - .getByRole("menu", { name: "Message actions" }) - .getByRole("menuitem", { name: "Edit" }) - .click(); - const edited = `Edited synthetic two-session message ${Date.now()}`; - await first.getByLabel("Edit message").fill(edited); - await first.getByRole("button", { name: "Save changes" }).click(); - await expect( - second.locator("#room-timeline").getByText(edited).last(), - ).toBeVisible(); - - const idempotencyKey = `browser-duplicate-${newId()}`; - const duplicateBody = { - document: { - type: "doc", - content: [ - { - type: "paragraph", - content: [{ type: "text", text: "Synthetic duplicate-safe post" }], - }, - ], - }, - plainText: "Synthetic duplicate-safe post", - messageType: "text", - dataClassification: "internal", - idempotencyKey, - }; - const [duplicateOne, duplicateTwo] = await Promise.all([ - first.request.post(`/api/v1/rooms/${room.id}/messages`, { - data: duplicateBody, - }), - first.request.post(`/api/v1/rooms/${room.id}/messages`, { - data: duplicateBody, - }), - ]); - expect([duplicateOne.status(), duplicateTwo.status()].sort()).toEqual([ - 200, 201, - ]); - expect((await duplicateOne.json()).data.id).toBe( - (await duplicateTwo.json()).data.id, - ); - - const unsafe = await first.request.post( - `/api/v1/rooms/${room.id}/messages`, - { - data: { - ...duplicateBody, - idempotencyKey: `unsafe-${newId()}`, - document: { - type: "doc", - content: [ - { - type: "paragraph", - content: [ - { - type: "text", - text: "unsafe", - marks: [ - { - type: "link", - attrs: { href: "javascript:alert(1)" }, - }, - ], - }, - ], - }, - ], - }, - }, - }, - ); - expect(unsafe.status()).toBe(400); - - const structuredText = `Synthetic structured alert ${Date.now()}`; - const structured = await first.request.post( - `/api/v1/rooms/${room.id}/messages`, - { - data: { - ...duplicateBody, - plainText: structuredText, - messageType: "alert", - idempotencyKey: `structured-${newId()}`, - }, - }, - ); - expect(structured.status()).toBe(201); - const structuredCard = first - .getByRole("article") - .filter({ hasText: structuredText }); - await expect( - structuredCard.getByText("Security alert", { exact: true }).first(), - ).toBeVisible(); - await structuredCard.getByText("Record details").click(); - await expect(structuredCard.getByText("internal")).toBeVisible(); - - const unauthenticated = await playwrightRequest.newContext({ - baseURL, - storageState: { cookies: [], origins: [] }, - extraHTTPHeaders: { cookie: "" }, - }); - expect( - (await unauthenticated.get(`/api/v1/rooms/${room.id}/messages`)).status(), - ).toBe(401); - await unauthenticated.dispose(); - - const accessibility = await new AxeBuilder({ page: first }) - .include("#room-timeline") - .analyze(); - expect(accessibility.violations).toEqual([]); - } finally { - await Promise.allSettled([firstContext.close(), secondContext.close()]); - } -}); - -test("failed sends retry with the original idempotency key", async ({ - page, -}, testInfo) => { - test.skip(testInfo.project.name !== "chromium"); - await page.goto("/rooms/investigation-suspicious-powershell"); - const message = `Synthetic recoverable send ${Date.now()}`; - let failedIdempotencyKey: string | undefined; - const messagesEndpoint = "**/api/v1/rooms/*/messages"; - await page.route(messagesEndpoint, async (route) => { - if (route.request().method() !== "POST") { - await route.continue(); - return; - } - failedIdempotencyKey = ( - route.request().postDataJSON() as { idempotencyKey: string } - ).idempotencyKey; - await route.abort("failed"); - }); - const composer = page.locator(".tiptap"); - await composer.fill(message); - await page.keyboard.press("Enter"); - await expect( - page - .getByRole("alert") - .filter({ hasText: "Message failed. Draft preserved." }), - ).toBeVisible(); - await expect(composer).toContainText(message); - await page.unroute(messagesEndpoint); - - const retryRequest = page.waitForRequest( - (request) => - request.method() === "POST" && - request.url().includes("/api/v1/rooms/") && - request.url().endsWith("/messages"), - ); - await page.getByRole("button", { name: "Retry" }).click(); - const retriedIdempotencyKey = ( - (await retryRequest).postDataJSON() as { idempotencyKey: string } - ).idempotencyKey; - expect(retriedIdempotencyKey).toBe(failedIdempotencyKey); - await expect(page.getByText(message).last()).toBeVisible(); - const [persisted] = await database() - .select({ value: count() }) - .from(schema.messages) - .where(eq(schema.messages.idempotencyKey, retriedIdempotencyKey)) - .limit(1); - expect(persisted?.value).toBe(1); -}); - -test("composer preserves send preference and renders safe links", async ({ - page, -}, testInfo) => { - test.skip(testInfo.project.name !== "chromium"); - await page.goto("/rooms/investigation-suspicious-powershell"); - const composer = page.locator(".tiptap"); - const suffix = newId(); - const messageText = `Synthetic explicit-send link ${suffix}`; - await page.getByLabel("Enter sends").uncheck(); - await composer.fill(`${messageText} `); - await page.keyboard.press("Enter"); - await expect(composer).toContainText("Synthetic explicit-send link"); - - await page.getByRole("button", { name: "Add link" }).click(); - await page.getByLabel("Link URL").fill("example.test/synthetic-reference"); - await page.getByRole("button", { name: "Apply link" }).click(); - await page.getByRole("button", { name: "Send", exact: true }).click(); - - const message = page.getByRole("article").filter({ hasText: messageText }); - await expect( - message.getByRole("link", { name: /example\.test\/synthetic-reference/ }), - ).toHaveAttribute("href", "https://example.test/synthetic-reference"); - await page.reload(); - await expect(page.getByLabel("Enter sends")).not.toBeChecked(); -}); - -test("composer uploads and persists governed evidence", async ({ - page, -}, testInfo) => { - test.skip(testInfo.project.name !== "chromium"); - const [room] = await database() - .select() - .from(schema.rooms) - .where(eq(schema.rooms.slug, "investigation-suspicious-powershell")) - .limit(1); - if (!room) throw new Error("Synthetic investigation room required"); - - await page.goto("/rooms/investigation-suspicious-powershell"); - const suffix = newId(); - const fileName = `synthetic-room-evidence-${suffix}.txt`; - const message = `Synthetic governed attachment ${suffix}`; - await page.route("**/api/v1/rooms/*/attachments", async (route) => { - await new Promise((resolve) => setTimeout(resolve, 150)); - await route.continue(); - }); - await page.getByLabel("Choose evidence files").setInputFiles({ - name: fileName, - mimeType: "text/plain", - buffer: Buffer.from(`Synthetic evidence payload ${suffix}`), - }); - await expect(page.getByLabel(`Uploading ${fileName}`)).toBeVisible(); - await expect(page.getByText("Stored · pending scan")).toBeVisible(); - await page.locator(".tiptap").fill(message); - await page.getByRole("button", { name: "Send", exact: true }).click(); - const posted = page.getByRole("article").filter({ hasText: message }); - await expect(posted.getByText(fileName)).toBeVisible(); - await expect( - posted.getByText("Stored evidence · pending scan"), - ).toBeVisible(); - await page.reload(); - await expect( - page.getByRole("article").filter({ hasText: message }).getByText(fileName), - ).toBeVisible(); - - const [stored] = await database() - .select({ - relatedRoomId: schema.evidence.relatedRoomId, - scanState: schema.evidence.scanState, - storageKey: schema.evidence.storageKey, - }) - .from(schema.evidence) - .where(eq(schema.evidence.fileName, fileName)) - .limit(1); - expect(stored).toMatchObject({ - relatedRoomId: room.id, - scanState: "pending", - storageKey: expect.stringContaining("/evidence/"), - }); -}); - -test("mobile composer requires the explicit send button", async ({ - page, - isMobile, -}, testInfo) => { - test.skip(!isMobile || testInfo.project.name !== "mobile"); - await page.goto("/rooms/investigation-suspicious-powershell"); - const message = `Synthetic mobile explicit send ${Date.now()}`; - const composer = page.locator(".tiptap"); - await composer.fill(message); - await page.keyboard.press("Enter"); - await expect(composer).toContainText(message); - await page.getByRole("button", { name: "Send", exact: true }).click(); - await expect(composer).toBeEmpty(); - await expect(page.getByText(message).last()).toBeVisible(); -}); diff --git a/tests/muster.spec.ts b/tests/muster.spec.ts deleted file mode 100644 index 7529ee1..0000000 --- a/tests/muster.spec.ts +++ /dev/null @@ -1,2151 +0,0 @@ -import { expect, test } from "@playwright/test"; -import AxeBuilder from "@axe-core/playwright"; -import { database, newId, schema } from "@muster/database"; -import { demoIds } from "@muster/database/seed-data"; -import { and, eq, inArray } from "drizzle-orm"; -import { login } from "./helpers"; - -test("local administrator can sign in", async ({ page }) => { - await page.context().clearCookies(); - await login(page); -}); - -test("product branding and PWA icon metadata are consistent", async ({ - page, -}) => { - const manifestResponse = await page.request.get("/manifest.webmanifest"); - expect(manifestResponse.ok()).toBe(true); - expect((await manifestResponse.json()).icons).toEqual([ - { - src: "/icons/muster-192.png", - sizes: "192x192", - type: "image/png", - purpose: "any", - }, - { - src: "/icons/muster-512.png", - sizes: "512x512", - type: "image/png", - purpose: "any", - }, - ]); - for (const size of [16, 32, 48, 96, 180, 192, 512]) { - const iconResponse = await page.request.get(`/icons/muster-${size}.png`); - expect(iconResponse.ok(), `${size}px icon`).toBe(true); - expect(iconResponse.headers()["content-type"]).toContain("image/png"); - } - - await page.goto("/login"); - await expect( - page - .getByRole("img", { name: "Muster shield and tree logo" }) - .filter({ visible: true }), - ).toBeVisible(); - const iconLinks = await page - .locator('link[rel="icon"]') - .evaluateAll((links) => links.map((link) => link.getAttribute("href"))); - expect(iconLinks).toEqual( - expect.arrayContaining([ - "/icons/muster-16.png", - "/icons/muster-32.png", - "/icons/muster-48.png", - "/icons/muster-96.png", - ]), - ); - await expect(page.locator('link[rel="apple-touch-icon"]')).toHaveAttribute( - "href", - "/icons/muster-180.png", - ); - await page.goto("/offline"); - await expect( - page.getByRole("img", { name: "Muster shield and tree logo" }), - ).toBeVisible(); - await page.goto("/rooms/soc-operations"); - await expect( - page.locator('img[src*="muster-32.png"]').first(), - ).toBeAttached(); -}); - -test("room workspace and command palette are accessible", async ({ page }) => { - await page.goto("/"); - await expect(page).toHaveURL(/\/rooms\/soc-operations$/); - await expect( - page.getByRole("heading", { name: "soc-operations" }), - ).toBeVisible(); - await page - .getByRole("button", { name: /Search Muster Demo Workspace/ }) - .click(); - await expect( - page.getByRole("dialog", { name: "Command palette" }), - ).toBeVisible(); - await page - .getByPlaceholder("Type a command or search rooms") - .fill("open #alerts"); - await page.getByRole("button", { name: /Open #alerts/ }).click(); - await expect(page.getByRole("heading", { name: "alerts" })).toBeVisible(); -}); - -test("keyboard users can operate the command palette", async ({ page }) => { - await page.goto("/rooms/soc-operations"); - await page.keyboard.press("Control+k"); - const palette = page.getByRole("dialog", { name: "Command palette" }); - await expect(palette).toBeVisible(); - const input = page.getByPlaceholder("Type a command or search rooms"); - await expect(input).toBeFocused(); - await page.keyboard.type("alerts"); - await page.keyboard.press("Enter"); - await expect(page.getByRole("heading", { name: "alerts" })).toBeVisible(); -}); - -test("room posts a durable message and receives its SSE update", async ({ - page, -}, testInfo) => { - await page.goto("/rooms/investigation-suspicious-powershell"); - await expect( - page.getByRole("heading", { name: "investigation-suspicious-powershell" }), - ).toBeVisible(); - const note = `Playwright message from ${testInfo.project.name} ${Date.now()}`; - const composer = page.locator(".tiptap"); - await composer.fill(note); - await page.keyboard.press("Shift+Enter"); - await page.keyboard.type("Second line preserved"); - await expect(composer).toContainText("Second line preserved"); - if (testInfo.project.name === "mobile") { - await page.getByRole("button", { name: "Send", exact: true }).click(); - } else { - await page.keyboard.press("Enter"); - } - await expect(page.getByTestId("live-event")).toContainText( - "room.message.created", - ); - await expect(composer).toBeEmpty(); - await expect(page.getByText(note).last()).toBeVisible(); - await page.reload(); - await expect(page.getByText(note).last()).toBeVisible(); -}); - -test("room reactions toggle and thread replies persist", async ({ - page, -}, testInfo) => { - await page.goto("/rooms/investigation-suspicious-powershell"); - const reaction = page.getByRole("button", { name: "Reviewing, 3" }); - await reaction.click(); - await expect( - page.getByRole("button", { name: "Reviewing, 2" }), - ).toBeVisible(); - await page.getByRole("button", { name: "Reviewing, 2" }).click(); - await expect( - page.getByRole("button", { name: "Reviewing, 3" }), - ).toBeVisible(); - - const replyText = `Persistent thread reply from ${testInfo.project.name} ${Date.now()}`; - await page.getByRole("button", { name: "3 replies" }).click(); - await page.getByLabel("Reply to thread").fill(replyText); - if (testInfo.project.name === "mobile") { - await page.getByRole("button", { name: "Reply", exact: true }).click(); - } else { - await page.keyboard.press("Enter"); - } - await expect(page.getByText(replyText)).toBeVisible(); - await page.reload(); - await expect(page.getByText(replyText)).toBeVisible(); -}); - -test("thread export copies and downloads complete governed Markdown", async ({ - page, - playwright, -}, testInfo) => { - test.skip(testInfo.project.name !== "chromium"); - const db = database(); - const [room] = await db - .select() - .from(schema.rooms) - .where(eq(schema.rooms.slug, "soc-operations")) - .limit(1); - if (!room) throw new Error("Seeded SOC operations room required"); - const [otherRoom] = await db - .select() - .from(schema.rooms) - .where( - and( - eq(schema.rooms.organisationId, room.organisationId), - eq(schema.rooms.slug, "detection-engineering"), - ), - ) - .limit(1); - if (!otherRoom) throw new Error("Second seeded room required"); - const [human] = await db - .select() - .from(schema.actors) - .where( - and( - eq(schema.actors.organisationId, room.organisationId), - eq(schema.actors.identityReference, "admin@muster.local"), - ), - ) - .limit(1); - const [agent] = await db - .select() - .from(schema.actors) - .where( - and( - eq(schema.actors.organisationId, room.organisationId), - eq(schema.actors.actorType, "agent"), - ), - ) - .limit(1); - const [system] = await db - .select() - .from(schema.actors) - .where( - and( - eq(schema.actors.organisationId, room.organisationId), - eq(schema.actors.actorType, "system"), - ), - ) - .limit(1); - if (!human || !agent || !system) { - throw new Error("Seeded human, agent, and system actors required"); - } - - const suffix = newId().slice(0, 8); - const messageIds = [newId(), newId(), newId(), newId(), newId()]; - const evidenceId = newId(); - const secret = `synthetic-thread-secret-${suffix}`; - const deletedCanary = `deleted-thread-canary-${suffix}`; - const otherRoomCanary = `other-room-thread-canary-${suffix}`; - const rootText = `Review synthetic thread ${suffix}`; - const startedAt = new Date(); - const textDocument = (text: string) => ({ - type: "doc", - content: [ - { - type: "paragraph", - content: [{ type: "text", text }], - }, - ], - }); - - try { - await db.insert(schema.evidence).values({ - id: evidenceId, - organisationId: room.organisationId, - fileName: `synthetic-thread-${suffix}.json`, - mimeType: "application/json", - size: 128, - sha256: `${"c".repeat(56)}${suffix}`.slice(0, 64), - uploadedByActorId: human.id, - classification: "internal", - relatedRoomId: room.id, - source: "synthetic-browser-test", - storageKey: `${room.organisationId}/evidence/${evidenceId}/synthetic.json`, - scanState: "clean", - retentionState: "active", - }); - await db.insert(schema.messages).values([ - { - id: messageIds[0]!, - organisationId: room.organisationId, - roomId: room.id, - authorActorId: human.id, - messageType: "text", - document: textDocument(rootText), - plainText: rootText, - createdAt: startedAt, - idempotencyKey: `synthetic-thread-root:${messageIds[0]}`, - }, - { - id: messageIds[1]!, - organisationId: room.organisationId, - roomId: room.id, - threadParentId: messageIds[0]!, - authorActorId: agent.id, - messageType: "text", - document: textDocument("Synthetic agent analysis completed."), - plainText: `Synthetic agent analysis completed. api_key=${secret}`, - createdAt: new Date(startedAt.getTime() + 1_000), - idempotencyKey: `synthetic-thread-agent:${messageIds[1]}`, - }, - { - id: messageIds[2]!, - organisationId: room.organisationId, - roomId: room.id, - threadParentId: messageIds[0]!, - authorActorId: system.id, - messageType: "finding", - document: { - type: "doc", - content: [ - { - type: "paragraph", - content: [{ type: "text", text: "Synthetic finding verified." }], - }, - { - type: "attachment", - attrs: { - id: evidenceId, - label: `synthetic-thread-${suffix}.json`, - }, - }, - ], - }, - plainText: "Synthetic finding verified.", - createdAt: new Date(startedAt.getTime() + 2_000), - idempotencyKey: `synthetic-thread-finding:${messageIds[2]}`, - }, - { - id: messageIds[3]!, - organisationId: room.organisationId, - roomId: room.id, - threadParentId: messageIds[0]!, - authorActorId: human.id, - messageType: "text", - document: textDocument(deletedCanary), - plainText: deletedCanary, - createdAt: new Date(startedAt.getTime() + 3_000), - deletedAt: new Date(startedAt.getTime() + 4_000), - idempotencyKey: `synthetic-thread-deleted:${messageIds[3]}`, - }, - { - id: messageIds[4]!, - organisationId: room.organisationId, - roomId: otherRoom.id, - threadParentId: messageIds[0]!, - authorActorId: human.id, - messageType: "text", - document: textDocument(otherRoomCanary), - plainText: otherRoomCanary, - createdAt: new Date(startedAt.getTime() + 4_000), - idempotencyKey: `synthetic-thread-other-room:${messageIds[4]}`, - }, - ]); - - await page.goto(`/rooms/${room.slug}?thread=${messageIds[0]}`); - await expect(page.getByRole("heading", { name: "Thread" })).toBeVisible(); - await page.evaluate(() => { - Object.defineProperty(navigator, "clipboard", { - configurable: true, - value: { - writeText: async (text: string) => { - ( - globalThis as typeof globalThis & { - __musterCopiedThread?: string; - } - ).__musterCopiedThread = text; - }, - }, - }); - }); - await page.getByRole("button", { name: "Thread actions" }).click(); - await page.getByRole("menuitem", { name: "Copy thread" }).click(); - await expect( - page - .getByRole("status") - .filter({ hasText: "Thread copied as Markdown." }), - ).toBeVisible(); - const markdown = await page.evaluate( - () => - ( - globalThis as typeof globalThis & { - __musterCopiedThread?: string; - } - ).__musterCopiedThread, - ); - expect(markdown).toContain(`# ${room.displayName} thread`); - expect(markdown).toContain(rootText); - expect(markdown).toContain(`${human.displayName} (Human)`); - expect(markdown).toContain(`${agent.displayName} (Agent)`); - expect(markdown).toContain(`${system.displayName} (System)`); - expect(markdown).toContain("**Entry type:** Investigation finding"); - expect(markdown).toContain(`/api/v1/evidence/${evidenceId}`); - expect(markdown).toContain("\\[REDACTED\\]"); - expect(markdown).not.toContain(secret); - expect(markdown).not.toContain(deletedCanary); - expect(markdown).not.toContain(otherRoomCanary); - expect(markdown?.match(/^## Reply /gm)).toHaveLength(2); - - await page.evaluate(() => { - Object.defineProperty(navigator, "clipboard", { - configurable: true, - value: { - writeText: async () => { - throw new Error("Synthetic clipboard denial"); - }, - }, - }); - }); - await page.getByRole("button", { name: "Thread actions" }).click(); - const downloadPromise = page.waitForEvent("download"); - await page.getByRole("menuitem", { name: "Copy thread" }).click(); - const download = await downloadPromise; - expect(download.suggestedFilename()).toBe( - `${room.slug}-thread-${messageIds[0]!.slice(0, 8)}.md`, - ); - await expect( - page - .getByRole("status") - .filter({ hasText: "Clipboard unavailable; Markdown downloaded." }), - ).toBeVisible(); - - const audits = await db - .select({ id: schema.auditEvents.id }) - .from(schema.auditEvents) - .where( - and( - eq(schema.auditEvents.organisationId, room.organisationId), - eq(schema.auditEvents.action, "room.thread.exported"), - eq(schema.auditEvents.targetId, messageIds[0]!), - ), - ); - expect(audits).toHaveLength(2); - - const baseURL = testInfo.project.use.baseURL?.toString(); - if (!baseURL) throw new Error("Playwright baseURL required"); - const anonymous = await playwright.request.newContext({ - baseURL, - storageState: { cookies: [], origins: [] }, - }); - try { - expect( - ( - await anonymous.get( - `/api/v1/rooms/${room.id}/threads/${messageIds[0]}/export`, - ) - ).status(), - ).toBe(401); - } finally { - await anonymous.dispose(); - } - } finally { - await db - .delete(schema.messages) - .where(inArray(schema.messages.id, messageIds)); - await db.delete(schema.evidence).where(eq(schema.evidence.id, evidenceId)); - } -}); - -test("composer drafts persist and theme control works", async ({ - page, -}, testInfo) => { - const draft = `Unsent draft ${testInfo.project.name} ${Date.now()}`; - await page.goto("/rooms/soc-operations"); - const composer = page.locator(".tiptap"); - await composer.fill(draft); - await page.getByRole("button", { name: "User menu and theme" }).click(); - await page.getByRole("menuitem", { name: "Use light theme" }).click(); - await expect(page.locator("html")).toHaveAttribute("data-theme", "light"); - await page.reload(); - await expect(composer).toContainText(draft); -}); - -test("investigation exposes hypotheses, findings, and promotion approval", async ({ - page, -}, testInfo) => { - await page.goto("/investigations/INV-2026-0178/hypotheses"); - await expect( - page.getByText("Stolen portal credentials were used"), - ).toBeVisible(); - await page.getByRole("link", { name: "Findings" }).click(); - await expect( - page.getByText("Encoded PowerShell retrieved second-stage content"), - ).toBeVisible(); - await page.getByRole("button", { name: /Promote to Kelpie/ }).click(); - const promotionReadiness = - testInfo.project.name === "mobile" - ? page.locator("#promotion-readiness") - : page.locator(".context-panel"); - await expect( - promotionReadiness.getByText( - "Human approval required before case creation.", - ), - ).toBeVisible(); -}); - -test("agent learning is evidence-backed and approval gated", async ({ - page, -}, testInfo) => { - const agentId = demoIds.actors.triage; - const sourceRunId = newId(); - const skillKey = `browser-governance-${testInfo.project.name}-${Date.now()}`; - const [definition] = await database() - .select() - .from(schema.agentDefinitions) - .where(eq(schema.agentDefinitions.id, agentId)) - .limit(1); - if (!definition) throw new Error("Seeded triage agent required"); - await database() - .insert(schema.agentRuns) - .values({ - id: sourceRunId, - organisationId: definition.organisationId, - agentId, - requestedByActorId: definition.ownerActorId, - investigationId: null, - trigger: "browser_governance_test", - status: "completed", - request: { traceId: `browser-learning-${sourceRunId}` }, - progress: { stage: "completed", percent: 100 }, - startedAt: new Date(), - completedAt: new Date(), - inputHash: "a".repeat(64), - outputHash: "b".repeat(64), - promptVersion: definition.systemPromptVersion, - runtime: "mock", - model: definition.model, - maximumRuntimeSeconds: definition.maximumRuntimeSeconds, - maximumTokenBudget: definition.maximumTokenBudget, - maximumCostCents: definition.maximumCostCents, - idempotencyKey: `browser-learning:${sourceRunId}`, - }); - const allowedTools = Array.isArray(definition.allowedTools) - ? definition.allowedTools.filter( - (value): value is string => typeof value === "string", - ) - : []; - const requiredCapabilities = Array.isArray(definition.capabilityRequirements) - ? definition.capabilityRequirements.filter( - (value): value is string => typeof value === "string", - ) - : []; - const proposed = await page.request.post( - `/api/v1/agents/${agentId}/learning`, - { - data: { - action: "propose_skill", - sourceRunId, - proposal: { - skillKey, - name: `Browser governed skill ${testInfo.project.name}`, - description: - "Synthetic browser proposal proving evaluation and human approval.", - content: `# Browser governed procedure\n\nSynthetic proposal ${skillKey}. Read only organisation-scoped evidence supplied by Muster. Compare identifiers and timestamps, cite every supporting record, preserve contradictions, and return uncertainty for human review. Never perform an external action.`, - changeRationale: - "A synthetic completed run demonstrates the governed browser review.", - evidenceReferences: [`agent-run:${sourceRunId}`], - requiredCapabilities, - allowedTools, - }, - }, - }, - ); - expect(proposed.status()).toBe(200); - - await page.goto(`/agents/${agentId}/learning`); - await expect(page.getByText("Governed continuous learning")).toBeVisible(); - await expect(page.getByText("Evaluation + human approval")).toBeVisible(); - await expect(page.getByText("Never self-authorised")).toBeVisible(); - const proposal = page.getByRole("article").filter({ hasText: skillKey }); - await proposal.getByRole("button", { name: "Evaluate" }).click(); - await expect(proposal.getByText("100 / 100")).toBeVisible(); - await proposal.getByRole("button", { name: "Publish" }).click(); - await expect(proposal.getByText("active", { exact: true })).toBeVisible(); - await page.getByRole("button", { name: "Kill switch" }).click(); - await expect( - page.getByRole("button", { name: "Restore agent" }), - ).toBeVisible(); - await page.getByRole("button", { name: "Restore agent" }).click(); - await expect(page.getByRole("button", { name: "Kill switch" })).toBeVisible(); - await proposal.getByRole("button", { name: "Retire" }).click(); - await expect( - proposal.getByText("rolled_back", { exact: true }), - ).toBeVisible(); -}); - -test("agent readiness distinguishes ready, attention, unknown, and unauthorised views", async ({ - page, - playwright, -}, testInfo) => { - test.skip(testInfo.project.name !== "chromium"); - const db = database(); - const definitions = await db - .select() - .from(schema.agentDefinitions) - .where(eq(schema.agentDefinitions.organisationId, demoIds.organisation)) - .limit(3); - if (definitions.length < 3) { - throw new Error("Three synthetic agent definitions required"); - } - const processIdentity = `synthetic-readiness-process-${newId()}`; - const verifiedAt = new Date(Date.now() + 5 * 60_000); - const snapshotIds = definitions.map(() => newId()); - - try { - await db.insert(schema.agentReadinessSnapshots).values( - definitions.map((definition, index) => { - const requestedPermissionMode = - definition.requestedPermissionMode === "approval_gated" - ? "approval_gated" - : "read_only"; - return { - id: snapshotIds[index]!, - organisationId: definition.organisationId, - agentId: definition.id, - processIdentity, - gatewayState: index === 2 ? "unknown" : "reported", - authenticationState: "reported", - observerState: "reported", - lifecycleEvidenceState: "reported", - lifecycleState: "idle", - capabilityState: "reported", - toolState: "reported", - permissionState: "reported", - reportedRuntime: "mock", - reportedProvider: "synthetic", - reportedModel: "synthetic-readiness-model", - inputCapabilities: ["task", "investigation"], - outputCapabilities: ["schema-valid security result"], - availableCommands: ["run", "cancel"], - toolSources: ["synthetic"], - toolRiskClasses: ["read"], - requestedPermissionMode, - effectivePermissionMode: - index === 1 - ? requestedPermissionMode === "read_only" - ? "approval_gated" - : "read_only" - : requestedPermissionMode, - limitations: ["Synthetic browser evidence only"], - heartbeatAt: verifiedAt, - verifiedAt, - }; - }), - ); - - await page.goto("/agents"); - await expect( - page.getByRole("heading", { name: "Agent directory" }), - ).toBeVisible(); - const expectedStates = ["Ready", "Needs attention", "Unknown"]; - for (const [index, definition] of definitions.entries()) { - const card = page - .getByRole("link") - .filter({ has: page.getByRole("heading", { name: definition.name }) }); - await expect( - card.getByText(expectedStates[index]!, { exact: true }), - ).toBeVisible(); - await expect(card).toContainText(definition.description); - } - - await page.goto(`/agents/${definitions[0]!.id}`); - await expect( - page.getByRole("heading", { name: definitions[0]!.name }), - ).toBeVisible(); - await expect( - page.getByRole("heading", { name: "Ready", exact: true }), - ).toBeVisible(); - await page - .getByText("Capabilities, permissions, and verification details") - .click(); - await expect(page.getByText("Requested permission")).toBeVisible(); - await expect(page.getByText("Effective permission")).toBeVisible(); - await expect(page.getByText("gateway: reported")).toBeVisible(); - - await page.goto(`/agents/${definitions[1]!.id}`); - await expect( - page.getByRole("heading", { name: "Needs attention", exact: true }), - ).toBeVisible(); - await expect( - page.getByText("Requested and effective permission modes diverge.", { - exact: true, - }), - ).toBeVisible(); - - await page.goto(`/agents/${definitions[2]!.id}`); - await expect( - page.getByRole("heading", { name: "Unknown", exact: true }), - ).toBeVisible(); - await expect( - page.getByText("Required runtime evidence is unknown.", { exact: true }), - ).toBeVisible(); - - await page.goto("/tasks"); - await page.getByRole("button", { name: "New task" }).click(); - const readyOption = page - .getByLabel("Assign to") - .locator(`option[value="${definitions[0]!.id}"]`); - await expect(readyOption).toContainText("Ready"); - await expect(readyOption).toContainText( - definitions[0]!.description.slice(0, 30), - ); - - const baseURL = testInfo.project.use.baseURL?.toString(); - if (!baseURL) throw new Error("Playwright baseURL required"); - const anonymous = await playwright.request.newContext({ - baseURL, - storageState: { cookies: [], origins: [] }, - }); - try { - expect((await anonymous.get("/api/v1/agents")).status()).toBe(401); - expect( - ( - await anonymous.get(`/api/v1/agents/${definitions[0]!.id}/readiness`) - ).status(), - ).toBe(401); - } finally { - await anonymous.dispose(); - } - } finally { - await db - .delete(schema.agentReadinessSnapshots) - .where(inArray(schema.agentReadinessSnapshots.id, snapshotIds)); - } -}); - -test("room shows scoped live multi-agent activity and run timeline", async ({ - page, - playwright, -}, testInfo) => { - test.skip(testInfo.project.name !== "chromium"); - const db = database(); - const [room] = await db - .select() - .from(schema.rooms) - .where(eq(schema.rooms.slug, "soc-operations")) - .limit(1); - if (!room) throw new Error("Seeded SOC operations room required"); - const definitions = await db - .select() - .from(schema.agentDefinitions) - .where(eq(schema.agentDefinitions.organisationId, room.organisationId)) - .limit(3); - if (definitions.length < 3) { - throw new Error("Three synthetic agent definitions required"); - } - const [otherRoom] = await db - .select() - .from(schema.rooms) - .where( - and( - eq(schema.rooms.organisationId, room.organisationId), - eq(schema.rooms.slug, "detection-engineering"), - ), - ) - .limit(1); - if (!otherRoom) throw new Error("Second seeded room required"); - - const now = new Date(); - const runIds = [newId(), newId(), newId(), newId()]; - const taskIds = [newId(), newId()]; - const eventIds = [newId(), newId(), newId(), newId()]; - const syntheticSecret = `synthetic-room-secret-${newId()}`; - - try { - await db.insert(schema.agentRuns).values([ - { - id: runIds[0]!, - agentId: definitions[0]!.id, - organisationId: room.organisationId, - roomId: room.id, - requestedByActorId: definitions[0]!.ownerActorId, - trigger: "task", - status: "running", - progress: { stage: "analysis", percent: 35 }, - startedAt: new Date(now.getTime() - 65_000), - heartbeatAt: now, - leaseExpiresAt: new Date(now.getTime() + 5 * 60_000), - deadlineAt: new Date(now.getTime() + 10 * 60_000), - workerId: "synthetic-room-worker", - inputHash: `synthetic-input-${runIds[0]}`, - promptVersion: "synthetic-room-v1", - runtime: "mock", - model: "synthetic-room-model", - idempotencyKey: `synthetic-room-run:${runIds[0]}`, - }, - { - id: runIds[1]!, - agentId: definitions[1]!.id, - organisationId: room.organisationId, - roomId: room.id, - requestedByActorId: definitions[1]!.ownerActorId, - trigger: "task", - status: "running", - progress: { stage: "verification", percent: 60 }, - startedAt: new Date(now.getTime() - 35_000), - heartbeatAt: now, - leaseExpiresAt: new Date(now.getTime() + 5 * 60_000), - deadlineAt: new Date(now.getTime() + 10 * 60_000), - workerId: "synthetic-room-worker", - inputHash: `synthetic-input-${runIds[1]}`, - promptVersion: "synthetic-room-v1", - runtime: "mock", - model: "synthetic-room-model", - idempotencyKey: `synthetic-room-run:${runIds[1]}`, - }, - { - id: runIds[2]!, - agentId: definitions[2]!.id, - organisationId: room.organisationId, - roomId: room.id, - requestedByActorId: definitions[2]!.ownerActorId, - trigger: "task", - status: "running", - progress: { stage: "stale", percent: 10 }, - startedAt: new Date(now.getTime() - 10 * 60_000), - heartbeatAt: new Date(now.getTime() - 5 * 60_000), - leaseExpiresAt: new Date(now.getTime() + 5 * 60_000), - deadlineAt: new Date(now.getTime() + 10 * 60_000), - workerId: "synthetic-room-worker", - inputHash: `synthetic-input-${runIds[2]}`, - promptVersion: "synthetic-room-v1", - runtime: "mock", - model: "synthetic-room-model", - idempotencyKey: `synthetic-room-run:${runIds[2]}`, - }, - { - id: runIds[3]!, - agentId: definitions[2]!.id, - organisationId: room.organisationId, - roomId: otherRoom.id, - requestedByActorId: definitions[2]!.ownerActorId, - trigger: "task", - status: "running", - progress: { stage: "unrelated", percent: 50 }, - startedAt: new Date(now.getTime() - 20_000), - heartbeatAt: now, - leaseExpiresAt: new Date(now.getTime() + 5 * 60_000), - deadlineAt: new Date(now.getTime() + 10 * 60_000), - workerId: "synthetic-room-worker", - inputHash: `synthetic-input-${runIds[3]}`, - promptVersion: "synthetic-room-v1", - runtime: "mock", - model: "synthetic-room-model", - idempotencyKey: `synthetic-room-run:${runIds[3]}`, - }, - ]); - await db.insert(schema.tasks).values( - taskIds.map((id, index) => ({ - id, - organisationId: room.organisationId, - title: `Synthetic room activity ${index + 1}`, - status: "in_progress" as const, - createdByActorId: definitions[index]!.ownerActorId, - roomId: room.id, - idempotencyKey: `synthetic-room-task:${id}`, - agentRunId: runIds[index]!, - agentRunStatus: "running", - })), - ); - await db.insert(schema.agentRunEvents).values([ - { - id: eventIds[0]!, - organisationId: room.organisationId, - runId: runIds[0]!, - eventType: "prompt_prepared", - message: `Investigating synthetic room evidence api_key=${syntheticSecret}`, - createdAt: new Date(now.getTime() - 4_000), - }, - { - id: eventIds[1]!, - organisationId: room.organisationId, - runId: runIds[0]!, - eventType: "started", - message: "Agent run claimed for execution", - createdAt: new Date(now.getTime() - 2_000), - }, - { - id: eventIds[2]!, - organisationId: room.organisationId, - runId: runIds[1]!, - eventType: "prompt_prepared", - message: "Verifying synthetic controls and evidence", - createdAt: new Date(now.getTime() - 3_000), - }, - { - id: eventIds[3]!, - organisationId: room.organisationId, - runId: runIds[1]!, - eventType: "started", - message: "Agent run claimed for execution", - createdAt: new Date(now.getTime() - 1_000), - }, - ]); - - await page.goto(`/rooms/${room.slug}`); - const activity = page.getByTestId("room-agent-activity"); - await expect(activity).toContainText("2 agents working"); - await expect( - activity.getByRole("button", { name: "View all" }), - ).toBeVisible(); - await expect(activity).toContainText("[REDACTED]"); - await expect(activity).not.toContainText(syntheticSecret); - - await activity.getByRole("button", { name: "View all" }).click(); - let panel = page.getByTestId("agent-activity-panel"); - await expect(panel).toBeVisible(); - await expect( - panel.getByRole("heading", { name: definitions[0]!.name }), - ).toBeVisible(); - await expect( - panel.getByRole("heading", { name: definitions[1]!.name }), - ).toBeVisible(); - await expect( - panel.getByRole("heading", { name: definitions[2]!.name }), - ).toHaveCount(0); - - const firstCard = panel - .getByRole("article") - .filter({ hasText: definitions[0]!.name }); - await firstCard.getByRole("button", { name: "View" }).click(); - await expect( - panel.getByRole("heading", { name: "Run timeline" }), - ).toBeVisible(); - await expect( - panel.getByText("Investigating synthetic room evidence"), - ).toBeVisible(); - await expect( - panel.getByText("Agent run claimed for execution"), - ).toBeVisible(); - await expect(panel).not.toContainText(syntheticSecret); - - await page.keyboard.press("Escape"); - await expect(panel).toBeHidden(); - - await page.setViewportSize({ width: 390, height: 844 }); - await activity.getByRole("button", { name: "View all" }).click(); - panel = page.getByTestId("agent-activity-panel"); - await expect(panel).toBeVisible(); - expect((await panel.boundingBox())?.width).toBe(390); - await panel - .getByRole("button", { name: "Close agent activity panel" }) - .click(); - await expect(panel).toBeHidden(); - - await activity.getByRole("button", { name: "View all" }).click(); - await page.goto(`/rooms/${otherRoom.slug}`); - await expect(page.getByTestId("agent-activity-panel")).toHaveCount(0); - - await db - .update(schema.agentRuns) - .set({ heartbeatAt: new Date(Date.now() - 5 * 60_000) }) - .where(eq(schema.agentRuns.id, runIds[1]!)); - await page.goto(`/rooms/${room.slug}`); - const singleActivity = page.getByTestId("room-agent-activity"); - await expect(singleActivity).toContainText(definitions[0]!.name); - await expect( - singleActivity.getByRole("button", { name: "View all" }), - ).toHaveCount(0); - - const baseURL = testInfo.project.use.baseURL?.toString(); - if (!baseURL) throw new Error("Playwright baseURL required"); - const anonymous = await playwright.request.newContext({ - baseURL, - storageState: { cookies: [], origins: [] }, - }); - try { - expect( - ( - await anonymous.get(`/api/v1/rooms/${room.id}/agent-activity`) - ).status(), - ).toBe(401); - } finally { - await anonymous.dispose(); - } - } finally { - await db.delete(schema.tasks).where(inArray(schema.tasks.id, taskIds)); - await db - .delete(schema.agentRunEvents) - .where(inArray(schema.agentRunEvents.id, eventIds)); - await db - .delete(schema.agentRuns) - .where(inArray(schema.agentRuns.id, runIds)); - } -}); - -test("completed agent handoffs stay truthful, scoped, and inspectable", async ({ - page, - playwright, -}, testInfo) => { - test.skip(testInfo.project.name !== "chromium"); - const db = database(); - const [room] = await db - .select() - .from(schema.rooms) - .where(eq(schema.rooms.slug, "soc-operations")) - .limit(1); - if (!room) throw new Error("Seeded SOC operations room required"); - const [agent] = await db - .select() - .from(schema.agentDefinitions) - .where(eq(schema.agentDefinitions.organisationId, room.organisationId)) - .limit(1); - if (!agent) throw new Error("Synthetic agent definition required"); - - const suffix = newId().slice(0, 8); - const runIds = [newId(), newId(), newId()]; - const taskIds = [newId(), newId(), newId()]; - const eventIds = [newId(), newId(), newId()]; - const evidenceId = newId(); - const now = new Date(); - const titles = { - completed: `Synthetic completed handoff ${suffix}`, - blocked: `Synthetic blocked handoff ${suffix}`, - failed: `Synthetic failed handoff ${suffix}`, - }; - - try { - await db.insert(schema.evidence).values({ - id: evidenceId, - organisationId: room.organisationId, - fileName: `synthetic-handoff-${suffix}.json`, - mimeType: "application/json", - size: 128, - sha256: `${"a".repeat(56)}${suffix}`.slice(0, 64), - uploadedByActorId: agent.ownerActorId, - classification: "internal", - relatedRoomId: room.id, - source: "synthetic-browser-test", - storageKey: `${room.organisationId}/evidence/${evidenceId}/synthetic.json`, - scanState: "clean", - retentionState: "active", - }); - await db.insert(schema.agentRuns).values([ - { - id: runIds[0]!, - agentId: agent.id, - organisationId: room.organisationId, - roomId: room.id, - requestedByActorId: agent.ownerActorId, - trigger: "task", - status: "completed", - request: { humanRequest: "Confirm the synthetic endpoint is safe." }, - startedAt: new Date(now.getTime() - 60_000), - completedAt: now, - inputHash: `synthetic-handoff-input-${runIds[0]}`, - outputHash: "b".repeat(64), - promptVersion: "synthetic-handoff-v1", - runtime: "mock", - model: "synthetic-handoff-model", - structuredOutput: { - summary: "Synthetic endpoint activity was benign.", - evidenceReferences: [ - { - type: "muster.evidence", - reference: evidenceId, - sha256: null, - }, - ], - }, - idempotencyKey: `synthetic-handoff-run:${runIds[0]}`, - }, - { - id: runIds[1]!, - agentId: agent.id, - organisationId: room.organisationId, - roomId: room.id, - requestedByActorId: agent.ownerActorId, - trigger: "task", - status: "failed", - request: { humanRequest: "Publish the synthetic response action." }, - startedAt: new Date(now.getTime() - 50_000), - completedAt: now, - inputHash: `synthetic-handoff-input-${runIds[1]}`, - promptVersion: "synthetic-handoff-v1", - runtime: "mock", - model: "synthetic-handoff-model", - failureCode: "blocked_approval", - error: "Synthetic approval is required before publication.", - idempotencyKey: `synthetic-handoff-run:${runIds[1]}`, - }, - { - id: runIds[2]!, - agentId: agent.id, - organisationId: room.organisationId, - roomId: room.id, - requestedByActorId: agent.ownerActorId, - trigger: "task", - status: "failed", - request: { humanRequest: "Inspect the synthetic unavailable source." }, - startedAt: new Date(now.getTime() - 40_000), - completedAt: now, - inputHash: `synthetic-handoff-input-${runIds[2]}`, - promptVersion: "synthetic-handoff-v1", - runtime: "mock", - model: "synthetic-handoff-model", - failureCode: "synthetic_failure", - error: "Synthetic source was unavailable.", - idempotencyKey: `synthetic-handoff-run:${runIds[2]}`, - }, - ]); - await db.insert(schema.tasks).values([ - { - id: taskIds[0]!, - organisationId: room.organisationId, - title: titles.completed, - description: "Confirm the synthetic endpoint is safe.", - status: "review", - assignedActorId: agent.id, - createdByActorId: agent.ownerActorId, - roomId: room.id, - idempotencyKey: `synthetic-handoff-task:${taskIds[0]}`, - agentRunId: runIds[0]!, - agentRunStatus: "completed", - }, - { - id: taskIds[1]!, - organisationId: room.organisationId, - title: titles.blocked, - description: "Publish the synthetic response action.", - status: "ready", - assignedActorId: agent.id, - createdByActorId: agent.ownerActorId, - roomId: room.id, - idempotencyKey: `synthetic-handoff-task:${taskIds[1]}`, - agentRunId: runIds[1]!, - agentRunStatus: "failed", - }, - { - id: taskIds[2]!, - organisationId: room.organisationId, - title: titles.failed, - description: "Inspect the synthetic unavailable source.", - status: "ready", - assignedActorId: agent.id, - createdByActorId: agent.ownerActorId, - roomId: room.id, - idempotencyKey: `synthetic-handoff-task:${taskIds[2]}`, - agentRunId: runIds[2]!, - agentRunStatus: "failed", - }, - ]); - await db.insert(schema.agentRunEvents).values([ - { - id: eventIds[0]!, - organisationId: room.organisationId, - runId: runIds[0]!, - eventType: "verification_passed", - message: "Synthetic retained evidence matched the expected digest.", - }, - { - id: eventIds[1]!, - organisationId: room.organisationId, - runId: runIds[1]!, - eventType: "blocked", - message: "Synthetic approval gate blocked execution.", - }, - { - id: eventIds[2]!, - organisationId: room.organisationId, - runId: runIds[2]!, - eventType: "failed", - message: "Synthetic source retrieval failed.", - }, - ]); - - await page.goto("/tasks"); - const completedTask = page - .getByRole("article") - .filter({ hasText: titles.completed }) - .first(); - const blockedTask = page - .getByRole("article") - .filter({ hasText: titles.blocked }) - .first(); - const failedTask = page - .getByRole("article") - .filter({ hasText: titles.failed }) - .first(); - await expect( - completedTask.getByLabel("Agent handoff: Completed"), - ).toBeVisible(); - await expect( - completedTask.getByText("Synthetic endpoint activity was benign."), - ).toBeVisible(); - await expect( - completedTask.getByText( - /Persisted verification: Synthetic retained evidence/, - ), - ).toBeVisible(); - await expect( - completedTask.getByRole("link", { - name: `synthetic-handoff-${suffix}.json`, - }), - ).toHaveAttribute("href", `/api/v1/evidence/${evidenceId}`); - await expect( - blockedTask.getByLabel("Agent handoff: Blocked"), - ).toBeVisible(); - await expect( - blockedTask.getByText( - "Synthetic approval is required before publication.", - ), - ).toBeVisible(); - await expect(failedTask.getByLabel("Agent handoff: Failed")).toBeVisible(); - await expect( - failedTask.getByText("Synthetic source was unavailable."), - ).toBeVisible(); - - await completedTask - .getByRole("button", { name: "View full timeline" }) - .click(); - const timeline = page.getByRole("dialog", { - name: "Full agent run timeline", - }); - await expect(timeline).toBeVisible(); - await expect( - timeline.getByText( - "Synthetic retained evidence matched the expected digest.", - ), - ).toBeVisible(); - await timeline - .getByRole("button", { name: "Close full agent run timeline" }) - .click(); - - await page.goto("/rooms/soc-operations"); - const roomHandoffs = page.getByTestId("room-agent-handoffs"); - await expect(roomHandoffs).toContainText( - "Synthetic endpoint activity was benign.", - ); - await expect(roomHandoffs).toContainText( - "Synthetic approval is required before publication.", - ); - await expect(roomHandoffs).toContainText( - "Synthetic source was unavailable.", - ); - - const baseURL = testInfo.project.use.baseURL?.toString(); - if (!baseURL) throw new Error("Playwright baseURL required"); - const anonymous = await playwright.request.newContext({ - baseURL, - storageState: { cookies: [], origins: [] }, - }); - try { - for (const path of [ - `/api/v1/rooms/${room.id}/agent-handoffs`, - `/api/v1/agent-runs/${runIds[0]}/timeline`, - `/api/v1/evidence/${evidenceId}`, - ]) { - expect((await anonymous.get(path)).status(), path).toBe(401); - } - } finally { - await anonymous.dispose(); - } - } finally { - await db.delete(schema.tasks).where(inArray(schema.tasks.id, taskIds)); - await db - .delete(schema.agentRunEvents) - .where(inArray(schema.agentRunEvents.id, eventIds)); - await db - .delete(schema.agentRuns) - .where(inArray(schema.agentRuns.id, runIds)); - await db.delete(schema.evidence).where(eq(schema.evidence.id, evidenceId)); - } -}); - -test("task board manages durable agent work end to end", async ({ - page, -}, testInfo) => { - await page.goto("/tasks"); - await expect(page.getByRole("heading", { name: "Tasks" })).toBeVisible(); - await expect( - page.getByText("Threat hunt the unusual authentication alert"), - ).toBeVisible(); - - const taskTitle = `Review security signal ${testInfo.project.name} ${Date.now()}`; - const caseReference = `CASE-SYNTHETIC-${newId().slice(0, 8)}`; - await page.getByRole("button", { name: "New task" }).click(); - await page.getByPlaceholder("What needs doing?").fill(taskTitle); - await page - .getByPlaceholder("Context, constraints, and deliverable") - .fill("Correlate the signal and return an evidence-linked review draft."); - await page.getByLabel("Due").fill("2026-08-15T15:30"); - await page.getByLabel("Priority").selectOption("high"); - await page - .getByLabel("Room", { exact: true }) - .selectOption({ label: "#soc-operations" }); - await page.getByPlaceholder("Optional case ID").fill(caseReference); - await page.getByRole("button", { name: "Create task", exact: true }).click(); - await expect(page.getByText(taskTitle)).toBeVisible(); - - let taskCard = page.getByRole("article").filter({ hasText: taskTitle }); - await taskCard.getByRole("button", { name: `Edit ${taskTitle}` }).click(); - await page - .getByPlaceholder("Context, constraints, and deliverable") - .fill("Edited synthetic outcome with evidence and a human review."); - await page.getByRole("button", { name: "Save task" }).click(); - await expect( - taskCard.getByText("Edited synthetic outcome with evidence"), - ).toBeVisible(); - - await Promise.all([ - page.waitForResponse( - (response) => - response.request().method() === "PATCH" && - response.url().includes("/api/v1/tasks/"), - ), - page.getByRole("button", { name: `Move ${taskTitle} right` }).click(), - ]); - - await page.reload(); - const readyColumn = page - .getByRole("heading", { name: "Ready" }) - .locator("xpath=ancestor::section"); - await expect(readyColumn.getByText(taskTitle)).toBeVisible(); - await expect(readyColumn.getByText(`Case ${caseReference}`)).toBeVisible(); - - taskCard = page.getByRole("article").filter({ hasText: taskTitle }); - await taskCard.getByRole("button", { name: "Delegate" }).click(); - await expect(taskCard.getByText(/Agent (queued|running)/)).toBeVisible(); - await taskCard.getByRole("button", { name: "Cancel" }).click(); - await expect(taskCard.getByText("Agent cancelled")).toBeVisible(); - await expect(taskCard.getByRole("button", { name: "Retry" })).toBeVisible(); - - await taskCard.getByRole("button", { name: "Retry" }).click(); - const reviewColumn = page - .getByRole("heading", { name: "Review", exact: true }) - .locator("xpath=ancestor::section"); - await expect( - reviewColumn.getByRole("heading", { name: taskTitle, exact: true }), - ).toBeVisible({ timeout: 15_000 }); - taskCard = reviewColumn.getByRole("article").filter({ hasText: taskTitle }); - await expect(taskCard.getByText("Agent completed")).toBeVisible(); - await expect(taskCard.getByLabel("Agent handoff: Completed")).toBeVisible(); - await expect( - taskCard.getByText(/Synthetic analysis completed for:/), - ).toBeVisible(); - await expect( - taskCard.getByText("No persisted verification evidence was recorded."), - ).toBeVisible(); - await taskCard.getByRole("button", { name: "Mark done" }).click(); - const doneColumn = page - .getByRole("heading", { name: "Done", exact: true }) - .locator("xpath=ancestor::section"); - await expect( - doneColumn.getByRole("heading", { name: taskTitle, exact: true }), - ).toBeVisible(); -}); - -test("Parker task produces a reviewable, versioned report manifest", async ({ - page, -}, testInfo) => { - const db = database(); - const suffix = `${testInfo.project.name}-${Date.now()}`; - const taskId = newId(); - const title = `Synthetic Parker briefing ${suffix}`; - try { - await db.insert(schema.tasks).values({ - id: taskId, - organisationId: demoIds.organisation, - title, - description: - "Prepare an executive reporting brief from authoritative history.", - status: "ready", - priority: "normal", - assignedActorId: demoIds.actors.threatIntel, - createdByActorId: demoIds.actors.jordan, - roomId: demoIds.rooms.soc, - idempotencyKey: `parker-e2e:${suffix}`, - }); - await page.goto("/tasks"); - const task = page.getByRole("article").filter({ hasText: title }); - await expect(task).toBeVisible(); - await task.getByRole("button", { name: "Delegate" }).click(); - const review = page - .getByRole("heading", { name: "Review", exact: true }) - .locator("xpath=ancestor::section"); - await expect(review.getByText(title)).toBeVisible({ timeout: 15_000 }); - const reviewTask = review.getByRole("article").filter({ hasText: title }); - await expect(reviewTask.getByText("Parker report manifest")).toBeVisible(); - const [reviewResponse] = await Promise.all([ - page.waitForResponse( - (response) => - response.url().includes("/api/v1/reports/") && - response.url().endsWith("/review") && - response.request().method() === "POST", - ), - reviewTask.getByRole("button", { name: "Review report" }).click(), - ]); - expect(reviewResponse.ok()).toBe(true); - const [postResponse] = await Promise.all([ - page.waitForResponse( - (response) => - response.url().includes("/api/v1/reports/") && - response.url().endsWith("/post") && - response.request().method() === "POST", - ), - reviewTask.getByRole("button", { name: "Post to room" }).click(), - ]); - expect(postResponse.ok()).toBe(true); - - const [report] = await db - .select() - .from(schema.reportManifests) - .where(eq(schema.reportManifests.taskId, taskId)); - expect(report?.status).toBe("posted"); - expect(report?.version).toBe(1); - const versionResponse = await page.request.post( - `/api/v1/reports/${report?.id}/versions`, - ); - expect(versionResponse.ok()).toBe(true); - expect((await versionResponse.json()).data.version).toBe(2); - } finally { - const reports = await db - .select({ - id: schema.reportManifests.id, - agentRunId: schema.reportManifests.agentRunId, - postedMessageId: schema.reportManifests.postedMessageId, - }) - .from(schema.reportManifests) - .where(eq(schema.reportManifests.taskId, taskId)); - const reportIds = reports.map((report) => report.id); - const runIds = reports - .map((report) => report.agentRunId) - .filter((id): id is string => Boolean(id)); - if (reportIds.length) { - await db - .delete(schema.reportDeliveries) - .where(inArray(schema.reportDeliveries.reportId, reportIds)); - await db - .delete(schema.reportManifests) - .where(inArray(schema.reportManifests.id, reportIds)); - } - const messageIds = reports - .map((report) => report.postedMessageId) - .filter((id): id is string => Boolean(id)); - if (messageIds.length) - await db - .delete(schema.messages) - .where(inArray(schema.messages.id, messageIds)); - await db.delete(schema.tasks).where(eq(schema.tasks.id, taskId)); - if (runIds.length) { - await db - .delete(schema.agentRunEvents) - .where(inArray(schema.agentRunEvents.runId, runIds)); - await db - .delete(schema.agentRuns) - .where(inArray(schema.agentRuns.id, runIds)); - } - } -}); - -test("Parker schedule creates and displays a governed weekly task configuration", async ({ - page, -}) => { - const db = database(); - try { - await page.goto("/settings/parker-reports"); - await page - .getByLabel("Room ID") - .fill(demoIds.rooms.soc); - await page.getByRole("button", { name: "Create weekly schedule" }).click(); - await expect( - page.getByText("weekly leadership report").last(), - ).toBeVisible(); - } finally { - await db - .delete(schema.reportSchedules) - .where( - eq( - schema.reportSchedules.idempotencyKey, - `parker-ui:${demoIds.rooms.soc}:weekly:leadership:Australia/Sydney`, - ), - ); - } -}); - -test("task APIs deny cross-organisation references and runs", async ({ - page, -}, testInfo) => { - const db = database(); - const foreignOrganisationId = newId(); - const foreignActorId = newId(); - const foreignRoomId = newId(); - const foreignTaskId = newId(); - const foreignRunId = newId(); - const suffix = `${testInfo.project.name}-${Date.now()}`; - let ownTaskId: string | null = null; - - try { - await db.insert(schema.organisations).values({ - id: foreignOrganisationId, - name: `Synthetic foreign organisation ${suffix}`, - slug: `synthetic-foreign-${suffix}`, - }); - await db.insert(schema.actors).values({ - id: foreignActorId, - organisationId: foreignOrganisationId, - actorType: "human", - displayName: "Synthetic foreign analyst", - identityReference: `foreign-${suffix}@example.invalid`, - }); - await db.insert(schema.rooms).values({ - id: foreignRoomId, - organisationId: foreignOrganisationId, - name: `foreign-room-${suffix}`, - slug: `foreign-room-${suffix}`, - displayName: "Synthetic foreign room", - roomType: "operations", - createdByActorId: foreignActorId, - }); - await db.insert(schema.tasks).values({ - id: foreignTaskId, - organisationId: foreignOrganisationId, - title: "Synthetic foreign task", - idempotencyKey: `foreign-task:${suffix}`, - createdByActorId: foreignActorId, - roomId: foreignRoomId, - assignedActorId: foreignActorId, - agentRunId: foreignRunId, - agentRunStatus: "running", - }); - - for (const body of [ - { title: "Rejected foreign actor", assignedActorId: foreignActorId }, - { title: "Rejected foreign room", roomId: foreignRoomId }, - ]) { - const response = await page.request.post("/api/v1/tasks", { - data: body, - }); - expect(response.status()).toBe(404); - } - - const ownTask = await page.request.post("/api/v1/tasks", { - data: { title: `Synthetic tenant-bound task ${suffix}` }, - }); - expect(ownTask.status()).toBe(201); - ownTaskId = ((await ownTask.json()) as { data: { id: string } }).data.id; - - for (const body of [ - { assignedActorId: foreignActorId }, - { roomId: foreignRoomId }, - ]) { - const response = await page.request.patch(`/api/v1/tasks/${ownTaskId}`, { - data: body, - }); - expect(response.status()).toBe(404); - } - expect( - ( - await page.request.patch(`/api/v1/tasks/${foreignTaskId}`, { - data: { title: "Cross-tenant mutation refused" }, - }) - ).status(), - ).toBe(404); - expect( - ( - await page.request.post(`/api/v1/tasks/${foreignTaskId}/delegate`) - ).status(), - ).toBe(404); - expect( - (await page.request.get(`/api/v1/agent-runs/${foreignRunId}`)).status(), - ).toBe(404); - } finally { - if (ownTaskId) { - await db.delete(schema.tasks).where(eq(schema.tasks.id, ownTaskId)); - } - await db.delete(schema.tasks).where(eq(schema.tasks.id, foreignTaskId)); - await db.delete(schema.rooms).where(eq(schema.rooms.id, foreignRoomId)); - await db.delete(schema.actors).where(eq(schema.actors.id, foreignActorId)); - await db - .delete(schema.organisations) - .where(eq(schema.organisations.id, foreignOrganisationId)); - } -}); - -test("agent observer APIs redact secrets for authorised and unauthorised callers", async ({ - page, - playwright, -}, testInfo) => { - test.skip(testInfo.project.name !== "chromium"); - const db = database(); - const runId = newId(); - const taskId = newId(); - const eventId = newId(); - const canary = `synthetic-observer-secret-${newId()}`; - const [definition] = await db - .select() - .from(schema.agentDefinitions) - .where(eq(schema.agentDefinitions.organisationId, demoIds.organisation)) - .limit(1); - if (!definition) throw new Error("Seeded agent definition required"); - - try { - await db.insert(schema.agentRuns).values({ - id: runId, - organisationId: definition.organisationId, - agentId: definition.id, - requestedByActorId: definition.ownerActorId, - investigationId: null, - trigger: "browser_redaction_test", - status: "completed", - request: { traceId: `browser-redaction-${runId}` }, - progress: { stage: "completed", percent: 100, apiKey: canary }, - startedAt: new Date(), - completedAt: new Date(), - inputHash: "c".repeat(64), - outputHash: "d".repeat(64), - outputSchema: "ExecutiveUpdate", - structuredOutput: { - headline: "Synthetic useful observer evidence", - client_secret: canary, - }, - error: `Authorization: Bearer ${canary}`, - promptVersion: definition.systemPromptVersion, - runtime: "mock", - model: definition.model, - maximumRuntimeSeconds: definition.maximumRuntimeSeconds, - maximumTokenBudget: definition.maximumTokenBudget, - maximumCostCents: definition.maximumCostCents, - idempotencyKey: `browser-redaction:${runId}`, - }); - await db.insert(schema.tasks).values({ - id: taskId, - organisationId: definition.organisationId, - title: "Synthetic observer redaction task", - idempotencyKey: `browser-redaction-task:${taskId}`, - createdByActorId: definition.ownerActorId, - agentRunId: runId, - agentRunStatus: "completed", - }); - await db.insert(schema.agentRunEvents).values({ - id: eventId, - organisationId: definition.organisationId, - runId, - eventType: "synthetic_observer_test", - message: `Cookie: session=${canary}`, - payload: { refreshToken: canary, evidenceCount: 3 }, - }); - - for (const path of ["/api/v1/tasks", `/api/v1/agent-runs/${runId}`]) { - const response = await page.request.get(path, { - headers: { "x-trace-id": `password=${canary}` }, - }); - expect(response.ok(), path).toBe(true); - const body = await response.text(); - expect(body).not.toContain(canary); - expect(body).toContain("[REDACTED]"); - expect(body).toContain("Synthetic useful observer evidence"); - } - - const baseURL = testInfo.project.use.baseURL?.toString(); - if (!baseURL) throw new Error("Playwright baseURL required"); - const anonymous = await playwright.request.newContext({ - baseURL, - storageState: { cookies: [], origins: [] }, - }); - try { - for (const path of ["/api/v1/tasks", `/api/v1/agent-runs/${runId}`]) { - const response = await anonymous.get(path, { - headers: { "x-trace-id": `password=${canary}` }, - }); - expect(response.status(), path).toBe(401); - const body = await response.text(); - expect(body).not.toContain(canary); - } - } finally { - await anonymous.dispose(); - } - - const [persisted] = await db - .select({ - structuredOutput: schema.agentRuns.structuredOutput, - error: schema.agentRuns.error, - }) - .from(schema.agentRuns) - .where(eq(schema.agentRuns.id, runId)) - .limit(1); - expect(JSON.stringify(persisted)).toContain(canary); - } finally { - await db - .delete(schema.agentRunEvents) - .where(eq(schema.agentRunEvents.id, eventId)); - await db.delete(schema.tasks).where(eq(schema.tasks.id, taskId)); - await db.delete(schema.agentRuns).where(eq(schema.agentRuns.id, runId)); - } -}); - -test("live search filters persist in URL history and exclude private rooms", async ({ - page, - playwright, -}, testInfo) => { - test.skip(testInfo.project.name !== "chromium"); - const db = database(); - const [requester] = await db - .select() - .from(schema.actors) - .where(eq(schema.actors.identityReference, "admin@muster.local")) - .limit(1); - const [visibleRoom] = await db - .select() - .from(schema.rooms) - .where(eq(schema.rooms.slug, "soc-operations")) - .limit(1); - if (!requester || !visibleRoom) { - throw new Error("Seeded search requester and room required"); - } - - const suffix = newId().slice(-8); - const authorId = newId(); - const authorIdentity = `search-${authorId}@example.invalid`; - const privateRoomId = newId(); - const messageIds = [newId(), newId(), newId()]; - const authorName = `Synthetic Search Author ${suffix}`; - const canary = `searchbrowsercanary${suffix}`; - const visibleInside = `${canary} visible inside date window`; - const visibleBefore = `${canary} visible before date window`; - const privateInside = `${canary} private excluded result`; - - try { - await db.insert(schema.actors).values({ - id: authorId, - organisationId: requester.organisationId, - actorType: "human", - displayName: authorName, - identityReference: authorIdentity, - capabilityAssignments: ["rooms.read"], - }); - await db.insert(schema.rooms).values({ - id: privateRoomId, - organisationId: requester.organisationId, - name: `synthetic-search-private-${suffix}`, - slug: `synthetic-search-private-${suffix}`, - displayName: `Synthetic Search Private ${suffix}`, - roomType: "private", - visibility: "private", - createdByActorId: requester.id, - }); - await db.insert(schema.roomMemberships).values([ - { - organisationId: requester.organisationId, - roomId: visibleRoom.id, - actorId: authorId, - membershipRole: "member", - }, - { - organisationId: requester.organisationId, - roomId: privateRoomId, - actorId: authorId, - membershipRole: "owner", - }, - ]); - await db.insert(schema.messages).values([ - { - id: messageIds[0]!, - organisationId: requester.organisationId, - roomId: visibleRoom.id, - authorActorId: authorId, - messageType: "text", - document: { type: "doc", content: [] }, - plainText: visibleInside, - createdAt: new Date("2026-07-15T12:00:00.000Z"), - }, - { - id: messageIds[1]!, - organisationId: requester.organisationId, - roomId: visibleRoom.id, - authorActorId: authorId, - messageType: "text", - document: { type: "doc", content: [] }, - plainText: visibleBefore, - createdAt: new Date("2026-06-30T23:59:59.000Z"), - }, - { - id: messageIds[2]!, - organisationId: requester.organisationId, - roomId: privateRoomId, - authorActorId: authorId, - messageType: "text", - document: { type: "doc", content: [] }, - plainText: privateInside, - createdAt: new Date("2026-07-15T12:00:00.000Z"), - }, - ]); - - await page.goto("/search"); - await expect( - page.getByRole("heading", { name: "Search your workspace" }), - ).toBeVisible(); - const input = page.getByPlaceholder( - "Search or filter with from:, in:, after:, before:", - ); - const filteredQuery = `${canary} from:"${authorIdentity}" in:"${visibleRoom.displayName}" after:2026-07-01 before:2026-08-01`; - await input.fill(filteredQuery); - const initialSearch = page.waitForResponse( - (response) => - response.url().includes("/api/v1/search?q=") && - response.request().method() === "GET", - ); - await page.getByRole("button", { name: "Search", exact: true }).click(); - expect((await initialSearch).ok()).toBe(true); - await expect(page.getByText(visibleInside)).toBeVisible(); - await expect(page.getByText(visibleBefore)).toHaveCount(0); - await expect(page.getByText(privateInside)).toHaveCount(0); - await expect( - page.getByRole("button", { name: "Remove from filter" }), - ).toContainText(authorName); - await expect( - page.getByRole("button", { name: "Remove in filter" }), - ).toContainText(visibleRoom.displayName); - await expect(page).toHaveURL(/after%3A2026-07-01/); - - const filterRemovalSearch = page.waitForResponse((response) => - response.url().includes("/api/v1/search?q="), - ); - await page.getByRole("button", { name: "Remove after filter" }).click(); - expect((await filterRemovalSearch).ok()).toBe(true); - await expect(page.getByText(visibleBefore)).toBeVisible(); - await expect(page.getByText(privateInside)).toHaveCount(0); - const historySearch = page.waitForResponse((response) => - response.url().includes("/api/v1/search?q="), - ); - await page.goBack(); - expect((await historySearch).ok()).toBe(true); - await expect(page.getByText(visibleInside)).toBeVisible(); - await expect(page.getByText(visibleBefore)).toHaveCount(0); - const reloadSearch = page.waitForResponse((response) => - response.url().includes("/api/v1/search?q="), - ); - await page.reload(); - expect((await reloadSearch).ok()).toBe(true); - await expect(input).toHaveValue(filteredQuery); - await expect(page.getByText(visibleInside)).toBeVisible(); - - await input.fill(`${canary} from:"Unknown Synthetic Actor"`); - const invalidSearch = page.waitForResponse((response) => - response.url().includes("/api/v1/search?q="), - ); - await page.getByRole("button", { name: "Search", exact: true }).click(); - expect((await invalidSearch).status()).toBe(400); - await expect( - page - .getByRole("alert") - .filter({ hasText: "Unknown from: search filter" }), - ).toContainText('Unknown from: search filter "Unknown Synthetic Actor".'); - await expect(page.getByText(privateInside)).toHaveCount(0); - - const baseURL = testInfo.project.use.baseURL?.toString(); - if (!baseURL) throw new Error("Playwright baseURL required"); - const anonymous = await playwright.request.newContext({ - baseURL, - storageState: { cookies: [], origins: [] }, - }); - try { - expect((await anonymous.get(`/api/v1/search?q=${canary}`)).status()).toBe( - 401, - ); - } finally { - await anonymous.dispose(); - } - } finally { - await db - .delete(schema.messages) - .where(inArray(schema.messages.id, messageIds)); - await db - .delete(schema.roomMemberships) - .where(eq(schema.roomMemberships.actorId, authorId)); - await db.delete(schema.rooms).where(eq(schema.rooms.id, privateRoomId)); - await db.delete(schema.actors).where(eq(schema.actors.id, authorId)); - } -}); - -test("approved visual reactions send in two interactions and fail closed", async ({ - page, - playwright, -}, testInfo) => { - test.skip(testInfo.project.name !== "chromium"); - const db = database(); - const adminEmail = - process.env.MUSTER_LOCAL_ADMIN_EMAIL ?? "admin@muster.local"; - const [requester] = await db - .select() - .from(schema.actors) - .where(eq(schema.actors.identityReference, adminEmail)) - .limit(1); - const [room] = await db - .select() - .from(schema.rooms) - .where( - and( - eq(schema.rooms.organisationId, requester?.organisationId ?? newId()), - eq(schema.rooms.slug, "soc-operations"), - ), - ) - .limit(1); - if (!requester || !room) { - throw new Error("Seeded reaction requester and room required"); - } - - const suffix = newId().slice(-8); - let packId: string | undefined; - let revisionId: string | undefined; - let assetId: string | undefined; - let digest: string | undefined; - const altText = `Synthetic animated acknowledgement ${suffix}`; - let messageId: string | undefined; - const animatedGif = Buffer.from( - "47494638396101000100800000000000ffffff" + - "21f90400000000002c0000000001000100000202440100" + - "21f90400000000002c0000000001000100000202440100" + - "3b", - "hex", - ); - - try { - await page.emulateMedia({ reducedMotion: "reduce" }); - - await page.goto("/settings/reaction-packs"); - await expect( - page.getByRole("heading", { name: "Visual reaction packs" }), - ).toBeVisible(); - await page.getByLabel("Pack name").fill(`Synthetic Browser Pack ${suffix}`); - await page.getByLabel("Pack slug").fill(`synthetic-browser-pack-${suffix}`); - await page.getByLabel("Asset key").fill("animated-acknowledgement"); - await page.getByLabel("Accessible alt text").fill(altText); - await page.getByLabel("Image asset").setInputFiles({ - name: "synthetic-animated-acknowledgement.gif", - mimeType: "image/gif", - buffer: animatedGif, - }); - const created = page.waitForResponse( - (response) => - response.url().endsWith("/api/v1/reaction-packs") && - response.request().method() === "POST", - ); - await page.getByRole("button", { name: "Store draft" }).click(); - const createResponse = await created; - expect(createResponse.status()).toBe(201); - const createdPayload = (await createResponse.json()) as { - data: { - pack: { id: string }; - revision: { id: string }; - asset: { id: string; sha256: string }; - }; - }; - packId = createdPayload.data.pack.id; - revisionId = createdPayload.data.revision.id; - assetId = createdPayload.data.asset.id; - digest = createdPayload.data.asset.sha256; - await expect(page.getByText(altText)).toBeVisible(); - await expect(page.getByText(digest)).toBeVisible(); - const approved = page.waitForResponse( - (response) => - response - .url() - .endsWith( - `/api/v1/reaction-packs/${packId}/revisions/${revisionId}/approve`, - ) && response.request().method() === "POST", - ); - await page.getByRole("button", { name: "Approve exact revision" }).click(); - expect((await approved).status()).toBe(200); - await expect( - page.getByText("Exact verified revision approved."), - ).toBeVisible(); - const assetResponse = await page.request.get( - `/api/v1/reaction-assets/${assetId}?revision=${revisionId}&digest=${digest}`, - ); - expect(assetResponse.status()).toBe(200); - expect(assetResponse.headers()["content-type"]).toContain("image/gif"); - expect(assetResponse.headers()["cache-control"]).toBe("private, no-cache"); - expect(assetResponse.headers().etag).toBe(`"sha256-${digest}"`); - - await page.goto(`/rooms/${room.slug}`); - await page.getByRole("button", { name: "Add visual reaction" }).click(); - const picker = page.getByRole("dialog", { name: "Visual reactions" }); - await expect(picker).toBeVisible(); - const sent = page.waitForResponse( - (response) => - response.url().endsWith(`/api/v1/rooms/${room.id}/messages`) && - response.request().method() === "POST", - ); - await picker.getByRole("button", { name: `Send ${altText}` }).click(); - const response = await sent; - expect(response.status()).toBe(201); - const payload = (await response.json()) as { - data: { id: string; document: unknown }; - }; - messageId = payload.data.id; - await expect( - page.getByRole("img", { - name: `${altText}. Animation paused for reduced motion.`, - }), - ).toBeVisible(); - - const [stored] = await db - .select({ - messageType: schema.messages.messageType, - relatedAlertId: schema.messages.relatedAlertId, - relatedInvestigationId: schema.messages.relatedInvestigationId, - plainText: schema.messages.plainText, - }) - .from(schema.messages) - .where( - and( - eq(schema.messages.organisationId, requester.organisationId), - eq(schema.messages.id, messageId), - ), - ) - .limit(1); - expect(stored).toEqual({ - messageType: "text", - relatedAlertId: null, - relatedInvestigationId: null, - plainText: `[Visual reaction: ${altText}]`, - }); - - await page.getByRole("button", { name: "Add visual reaction" }).click(); - await expect(picker).toBeVisible(); - await page.keyboard.press("Escape"); - await expect(picker).toBeHidden(); - - await page.route("**/api/v1/reaction-assets/**", async (route) => { - await route.fulfill({ status: 404, body: "Synthetic missing asset" }); - }); - await page.reload(); - await expect( - page.getByRole("img", { - name: `${altText}. Reaction unavailable.`, - }), - ).toBeVisible(); - - const baseURL = testInfo.project.use.baseURL?.toString(); - if (!baseURL) throw new Error("Playwright baseURL required"); - const anonymous = await playwright.request.newContext({ - baseURL, - storageState: { cookies: [], origins: [] }, - }); - try { - expect( - (await anonymous.get("/api/v1/reaction-packs/catalog")).status(), - ).toBe(401); - } finally { - await anonymous.dispose(); - } - } finally { - if (messageId) { - await db - .delete(schema.messages) - .where( - and( - eq(schema.messages.organisationId, requester.organisationId), - eq(schema.messages.id, messageId), - ), - ); - } - if (assetId) { - await db - .delete(schema.reactionPackAssets) - .where(eq(schema.reactionPackAssets.id, assetId)); - } - if (revisionId) { - await db - .delete(schema.reactionPackRevisions) - .where(eq(schema.reactionPackRevisions.id, revisionId)); - } - if (packId) { - await db - .delete(schema.reactionPacks) - .where(eq(schema.reactionPacks.id, packId)); - } - } -}); - -test("empty visual reaction picker points to organisation settings", async ({ - page, -}, testInfo) => { - test.skip(testInfo.project.name !== "chromium"); - await page.goto("/rooms/soc-operations"); - await page.getByRole("button", { name: "Add visual reaction" }).click(); - const picker = page.getByRole("dialog", { name: "Visual reactions" }); - await expect(picker.getByText("No approved packs installed")).toBeVisible(); - await expect( - picker.getByRole("link", { - name: "Open organisation reaction settings", - }), - ).toHaveAttribute("href", "/settings/reaction-packs"); -}); - -test("workflow YAML editor, integrations, search, and approvals render", async ({ - page, -}) => { - await page.goto("/workflows/suspicious-powershell-triage"); - await expect(page.getByText("Schema valid")).toBeVisible(); - await page.goto("/integrations/bower"); - await expect( - page.getByText("Heartbeat and queue state do not prove"), - ).toBeVisible(); - await page.goto("/search"); - await expect(page.getByText("0 permission-filtered results")).toBeVisible(); - await page.goto("/approvals"); - await expect(page.getByRole("heading", { name: "Approvals" })).toBeVisible(); - await expect(page.getByText("No approval records.")).toBeVisible(); -}); - -test("mobile navigation supports channel work", async ({ page }, testInfo) => { - test.skip(testInfo.project.name !== "mobile"); - await page.goto("/"); - await page.getByRole("button", { name: "Open navigation" }).click(); - await expect( - page.getByRole("navigation", { name: "Workspace" }), - ).toBeVisible(); - await page.getByRole("link", { name: /alerts/ }).click(); - await expect(page.getByRole("heading", { name: "alerts" })).toBeVisible(); -}); - -test("core workbench has no horizontal overflow at supported widths", async ({ - page, -}, testInfo) => { - test.skip(testInfo.project.name !== "chromium"); - - for (const width of [320, 375, 414, 768, 1280, 1920]) { - await page.setViewportSize({ width, height: 900 }); - await page.goto("/rooms/soc-operations"); - const overflow = await page.evaluate(() => ({ - clientWidth: document.documentElement.clientWidth, - scrollWidth: document.documentElement.scrollWidth, - })); - expect(overflow.scrollWidth, `${width}px viewport`).toBeLessThanOrEqual( - overflow.clientWidth, - ); - } -}); - -test("core workbench passes accessibility and console quality gates", async ({ - page, -}, testInfo) => { - test.skip(testInfo.project.name !== "chromium"); - const errors: string[] = []; - page.on("console", (message) => { - if (message.type() === "error") { - errors.push(message.text()); - } - }); - page.on("pageerror", (error) => errors.push(error.message)); - - for (const route of [ - "/rooms/soc-operations", - "/tasks", - "/investigations/INV-2026-0178", - "/settings", - "/workflows/suspicious-powershell-triage", - ]) { - await page.goto(route); - const results = await new AxeBuilder({ page }) - .withTags(["wcag2a", "wcag2aa", "wcag21a", "wcag21aa", "wcag22aa"]) - .analyze(); - expect( - results.violations, - `${route}: ${results.violations - .map((violation) => `${violation.id} (${violation.nodes.length})`) - .join(", ")}`, - ).toEqual([]); - } - - expect(errors).toEqual([]); -}); - -test("core workbench reflows at a 200 percent zoom equivalent", async ({ - page, -}, testInfo) => { - test.skip(testInfo.project.name !== "chromium"); - await page.setViewportSize({ width: 320, height: 720 }); - await page.goto("/rooms/soc-operations"); - await expect( - page.getByRole("heading", { name: "soc-operations" }), - ).toBeVisible(); - await expect( - page.getByRole("button", { name: "Open navigation" }), - ).toBeVisible(); - const overflow = await page.evaluate(() => ({ - clientWidth: document.documentElement.clientWidth, - scrollWidth: document.documentElement.scrollWidth, - })); - expect(overflow.scrollWidth).toBeLessThanOrEqual(overflow.clientWidth); -}); diff --git a/tests/rooms-governance.spec.ts b/tests/rooms-governance.spec.ts deleted file mode 100644 index c0977dd..0000000 --- a/tests/rooms-governance.spec.ts +++ /dev/null @@ -1,164 +0,0 @@ -import { expect, test } from "@playwright/test"; -import AxeBuilder from "@axe-core/playwright"; - -test("room browser is responsive, keyboard reachable, and accessible", async ({ - page, -}) => { - await page.goto("/rooms"); - await expect( - page.getByRole("heading", { name: "Rooms", exact: true }), - ).toBeVisible(); - await expect( - page.getByPlaceholder("Search name, purpose or topic"), - ).toBeVisible(); - await expect(page.getByRole("button", { name: "Create room" })).toBeVisible(); - await page.keyboard.press("Tab"); - await expect(page.locator(":focus")).toBeVisible(); - const results = await new AxeBuilder({ page }) - .include("main") - .withTags(["wcag2a", "wcag2aa", "wcag21a", "wcag21aa"]) - .analyze(); - expect(results.violations).toEqual([]); -}); - -test("administrator completes governed room lifecycle through the UI", async ({ - page, -}, testInfo) => { - test.skip(testInfo.project.name !== "chromium"); - const suffix = `${Date.now()}-${Math.random().toString(16).slice(2, 8)}`; - const roomName = `Synthetic Governance ${suffix}`; - const renamedRoom = `${roomName} Renamed`; - - await page.goto("/rooms"); - await page.getByRole("button", { name: "Create room" }).click(); - const createForm = page.locator("form").filter({ hasText: "Create room" }); - await createForm.getByLabel("Name").fill(roomName); - await createForm.getByLabel("Type").selectOption("engineering"); - await createForm.getByLabel("Visibility").selectOption("private"); - await createForm - .getByLabel("Topic") - .fill("Synthetic governance verification"); - await createForm - .getByLabel("Purpose") - .fill("Synthetic room used for enterprise governance regression coverage."); - await createForm.getByText("Guest invites").click(); - await createForm.getByText("Agent invites").click(); - await createForm.getByText("Export").click(); - await createForm.getByRole("button", { name: "Create", exact: true }).click(); - await expect(page.getByText(`${roomName} created`)).toBeVisible(); - - const card = page.getByRole("article").filter({ hasText: roomName }); - await expect(card).toBeVisible(); - await card.getByRole("button", { name: `Star ${roomName}` }).click(); - await expect( - card.getByRole("button", { name: `Remove ${roomName} from starred` }), - ).toBeVisible(); - await card.getByText("Sidebar group and order").click(); - await card.getByPlaceholder("Group").fill("Synthetic response"); - await card.getByLabel("Sidebar order").fill("12"); - await card.getByRole("button", { name: "Save" }).click(); - await expect( - page.getByRole("heading", { name: "Synthetic response" }), - ).toBeVisible(); - - await card.getByRole("link", { name: roomName }).click(); - await expect(page).toHaveURL(/\/rooms\/synthetic-governance-/); - await expect( - page.getByRole("heading", { name: roomName, level: 1 }), - ).toBeVisible(); - await page.getByRole("button", { name: "Room details", exact: true }).click(); - await expect(page.getByRole("tab", { name: "About" })).toBeVisible(); - await expect(page.getByLabel("Visibility")).toHaveValue("private"); - await page.getByLabel("Name").fill(renamedRoom); - await page - .getByLabel("Topic") - .fill("Synthetic governance topic updated through room details"); - await page.getByRole("button", { name: "Save room details" }).click(); - await expect(page.getByText(renamedRoom).first()).toBeVisible(); - - for (const tab of [ - "Members", - "Agents", - "Pinned", - "Files", - "Workflows", - "Integrations", - "Audit", - ]) { - await page.getByRole("tab", { name: tab }).click(); - await expect(page.getByRole("tab", { name: tab })).toHaveAttribute( - "aria-selected", - "true", - ); - } - await expect(page.getByText("room.updated").first()).toBeVisible(); - - await page.goto("/rooms"); - const renamedCard = page - .getByRole("article") - .filter({ hasText: renamedRoom }); - await renamedCard.getByRole("button", { name: "Archive" }).click(); - await expect( - page.getByRole("status").filter({ hasText: "archive complete" }), - ).toBeVisible(); - await expect(renamedCard).not.toBeVisible(); - await page.getByLabel("Archived").check(); - const archivedCard = page - .getByRole("article") - .filter({ hasText: renamedRoom }); - await expect( - archivedCard.getByText("Archived", { exact: true }), - ).toBeVisible(); - await archivedCard.getByRole("button", { name: "Restore" }).click(); - await expect( - page.getByRole("status").filter({ hasText: "restore complete" }), - ).toBeVisible(); - await expect( - archivedCard.getByText("Archived", { exact: true }), - ).not.toBeVisible(); - - await page.goto("/rooms/admin"); - await expect( - page.getByRole("heading", { name: "Room governance" }), - ).toBeVisible(); - for (const tab of ["Users", "Guests", "Agents", "Ownership", "Audit"]) { - await page.getByRole("tab", { name: tab }).click(); - await expect(page.getByRole("tab", { name: tab })).toHaveAttribute( - "aria-selected", - "true", - ); - } - await page.getByRole("tab", { name: "Ownership" }).click(); - await expect( - page.getByRole("main").getByText(renamedRoom).last(), - ).toBeVisible(); -}); - -test("direct room creation is duplicate-safe through the authenticated API", async ({ - request, -}) => { - const directory = await request.get("/api/v1/directory"); - expect(directory.ok()).toBe(true); - const directoryPayload = (await directory.json()) as { - data: Array<{ id: string; actorType: string; status: string }>; - }; - const participant = directoryPayload.data.find( - (actor) => actor.status === "active" && actor.actorType === "agent", - ); - test.skip(!participant, "Synthetic active agent required"); - const first = await request.post("/api/v1/rooms/direct", { - data: { - actorIds: [participant!.id], - idempotencyKey: `playwright-direct-first:${crypto.randomUUID()}`, - }, - }); - const second = await request.post("/api/v1/rooms/direct", { - data: { - actorIds: [participant!.id], - idempotencyKey: `playwright-direct-second:${crypto.randomUUID()}`, - }, - }); - expect([200, 201]).toContain(first.status()); - expect(second.status()).toBe(200); - expect((await second.json()).data.id).toBe((await first.json()).data.id); -}); diff --git a/tests/screenshots.spec.ts b/tests/screenshots.spec.ts deleted file mode 100644 index e1b3f37..0000000 --- a/tests/screenshots.spec.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { mkdir } from "node:fs/promises"; -import { test } from "@playwright/test"; - -const captures = [ - ["login", "/login"], - ["workspace", "/"], - ["room-soc-operations", "/rooms/soc-operations"], - ["room-alerts", "/rooms/alerts"], - ["room-incident", "/rooms/incident-KP-2026-0042"], - ["room-agent-collaboration", "/rooms/investigation-suspicious-powershell"], - ["direct-message", "/rooms/dm-maya-chen"], - ["agent-direct-message", "/rooms/dm-triage-agent"], - ["search", "/search"], - ["settings", "/settings"], -] as const; - -test("generate deterministic product screenshots", async ({ - page, -}, testInfo) => { - test.skip(testInfo.project.name !== "chromium"); - await mkdir("screenshots", { recursive: true }); - for (const [name, route] of captures) { - await page.goto(route); - await page.addStyleTag({ - content: '[data-dynamic-message="true"] { display: none !important; }', - }); - await page.screenshot({ - path: `screenshots/${name}.png`, - fullPage: true, - caret: "initial", - }); - } - - for (const width of [375, 768, 1280, 1920]) { - await page.setViewportSize({ width, height: 900 }); - await page.goto("/rooms/soc-operations"); - await page.addStyleTag({ - content: '[data-dynamic-message="true"] { display: none !important; }', - }); - await page.screenshot({ - path: `screenshots/room-dark-${width}.png`, - fullPage: true, - caret: "initial", - }); - await page.getByRole("button", { name: "User menu and theme" }).click(); - await page.screenshot({ - path: `screenshots/room-light-${width}.png`, - fullPage: true, - caret: "initial", - }); - } - - await page.setViewportSize({ width: 375, height: 844 }); - await page.goto("/rooms/alerts"); - await page.screenshot({ - path: "screenshots/mobile-alerts-channel.png", - fullPage: true, - caret: "initial", - }); -}); diff --git a/tests/slack-settings.spec.ts b/tests/slack-settings.spec.ts deleted file mode 100644 index 97b3ea1..0000000 --- a/tests/slack-settings.spec.ts +++ /dev/null @@ -1,441 +0,0 @@ -import { - expect, - test, - type APIRequestContext, - type Page, -} from "@playwright/test"; -import AxeBuilder from "@axe-core/playwright"; - -type SyntheticSettings = { - installations: Array>; - actors: Array<{ id: string; displayName: string }>; - agents: Array<{ id: string; name: string }>; - identities: Array>; - exposures: Array>; - deliveries: Array>; - encryptedBotToken?: string; - encryptedPayload?: string; -}; - -async function syntheticSettings(request: APIRequestContext) { - const response = await request.get("/api/v1/slack/settings"); - const body = (await response.json()) as { - data: { - actors: SyntheticSettings["actors"]; - agents: SyntheticSettings["agents"]; - }; - }; - expect(response.status()).toBe(200); - expect(body.data.actors.length).toBeGreaterThan(0); - expect(body.data.agents.length).toBeGreaterThan(0); - return { - installations: [ - { - id: "00000000-0000-4000-8000-000000000101", - teamId: "T-SYNTHETIC", - teamName: "Synthetic Security Workspace", - scopes: [ - "app_mentions:read", - "assistant:write", - "chat:write", - "commands", - "im:history", - ], - status: "active", - installedAt: "2026-07-27T01:00:00.000Z", - lastHealthAt: null, - lastDeliveryAt: "2026-07-27T01:05:00.000Z", - lastError: null, - }, - ], - actors: body.data.actors, - agents: body.data.agents, - identities: [], - exposures: [], - deliveries: [ - { - id: "00000000-0000-4000-8000-000000000102", - installationId: "00000000-0000-4000-8000-000000000101", - runId: "00000000-0000-4000-8000-000000000103", - status: "dead_letter", - attemptCount: 3, - lastError: "Slack chat.update failed: synthetic_channel_not_found", - updatedAt: "2026-07-27T01:10:00.000Z", - }, - ], - // Deliberately present in the fake API response. The UI must ignore both. - encryptedBotToken: "xoxb-synthetic-never-render", - encryptedPayload: "synthetic-payload-never-render", - } satisfies SyntheticSettings; -} - -async function mockSlackAdministration( - page: Page, - settings: SyntheticSettings, -) { - const calls: Array<{ method: string; path: string; body: unknown }> = []; - await page.route("**/api/v1/slack/**", async (route) => { - const request = route.request(); - const url = new URL(request.url()); - const method = request.method(); - const body = request.postDataJSON?.() ?? null; - calls.push({ method, path: url.pathname, body }); - - if (url.pathname.endsWith("/settings")) { - await route.fulfill({ status: 200, json: { data: settings } }); - return; - } - if (url.pathname.endsWith("/health")) { - settings.installations[0] = { - ...settings.installations[0], - lastHealthAt: "2026-07-27T01:15:00.000Z", - }; - await route.fulfill({ status: 200, json: { data: { ok: true } } }); - return; - } - if (url.pathname.endsWith("/install") && method === "GET") { - await route.fulfill({ - status: 200, - json: { - data: { - authorizationUrl: `${url.origin}/settings/slack?oauth=synthetic`, - }, - }, - }); - return; - } - if (url.pathname.endsWith("/install") && method === "DELETE") { - settings.installations[0] = { - ...settings.installations[0], - status: "revoked", - }; - await route.fulfill({ status: 200, json: { data: { revoked: true } } }); - return; - } - if (url.pathname.endsWith("/identities") && method === "POST") { - const input = body as { - installationId: string; - slackUserId: string; - actorId: string; - }; - const actor = settings.actors.find( - (candidate) => candidate.id === input.actorId, - )!; - settings.identities = [ - { - id: "00000000-0000-4000-8000-000000000104", - ...input, - actorName: actor.displayName, - status: "active", - createdAt: "2026-07-27T01:20:00.000Z", - }, - ]; - await route.fulfill({ status: 200, json: { data: { saved: true } } }); - return; - } - if (url.pathname.endsWith("/exposures") && method === "PUT") { - const input = body as Record; - const agent = settings.agents.find( - (candidate) => candidate.id === input.agentId, - )!; - settings.exposures = [ - { - id: "00000000-0000-4000-8000-000000000105", - ...input, - agentName: agent.name, - updatedAt: "2026-07-27T01:25:00.000Z", - }, - ]; - await route.fulfill({ status: 200, json: { data: { saved: true } } }); - return; - } - await route.fulfill({ status: 404, json: { detail: "Synthetic route" } }); - }); - return calls; -} - -test("Slack administration is protected and gives operators a safe empty state", async ({ - page, - playwright, - request, -}) => { - const anonymous = await playwright.request.newContext({ - baseURL: process.env.MUSTER_BASE_URL ?? "http://127.0.0.1:3000", - storageState: { cookies: [], origins: [] }, - }); - try { - expect((await anonymous.get("/api/v1/slack/settings")).status()).toBe(401); - } finally { - await anonymous.dispose(); - } - - const settings = await request.get("/api/v1/slack/settings"); - const settingsBody = await settings.text(); - expect(settings.status(), settingsBody).toBe(200); - expect(settingsBody).not.toContain("encryptedBotToken"); - expect(settingsBody).not.toContain("encryptedPayload"); - - await page.route("**/api/v1/slack/install", async (route) => { - const origin = new URL(route.request().url()).origin; - await route.fulfill({ - status: 200, - json: { - data: { - authorizationUrl: `${origin}/settings/slack?oauth=synthetic-install`, - }, - }, - }); - }); - await page.goto("/settings/slack"); - await expect( - page.getByRole("heading", { name: "Slack agent harness" }), - ).toBeVisible(); - await expect( - page.getByText("No Slack workspace is connected."), - ).toBeVisible(); - await expect( - page.getByText("No Slack identities are mapped yet."), - ).toBeVisible(); - await expect( - page.getByText("No agents are exposed to Slack yet."), - ).toBeVisible(); - await expect( - page.getByText("Connect an active Slack workspace before mapping users."), - ).toBeVisible(); - await expect( - page.getByText("Connect an active Slack workspace before exposing agents."), - ).toBeVisible(); - const results = await new AxeBuilder({ page }) - .include("main") - .withTags(["wcag2a", "wcag2aa", "wcag21a", "wcag21aa", "wcag22aa"]) - .analyze(); - expect(results.violations).toEqual([]); - - await page.getByRole("button", { name: "Connect Slack" }).click(); - await expect(page).toHaveURL(/oauth=synthetic-install/); -}); - -test("Slack operators can refresh, map, expose, reconnect, revoke, and reach the kill switch", async ({ - page, - request, -}, testInfo) => { - test.skip(testInfo.project.name !== "chromium"); - const settings = await syntheticSettings(request); - const calls = await mockSlackAdministration(page, settings); - await page.goto("/settings/slack"); - - const refresh = page.getByRole("button", { name: "Refresh diagnostics" }); - await refresh.focus(); - await expect(refresh).toBeFocused(); - await page.keyboard.press("Tab"); - await expect( - page.getByRole("button", { name: "Reconnect Slack" }), - ).toBeFocused(); - - await refresh.click(); - await expect(page.getByRole("status")).toContainText( - "Slack diagnostics refreshed.", - ); - await expect( - page - .getByRole("article") - .filter({ hasText: "Synthetic Security Workspace" }) - .getByText(/Health:/), - ).toContainText("2026"); - - const identityForm = page.locator('form[aria-label="Map a Slack user"]'); - await expect(identityForm).toHaveCount(1); - await identityForm - .locator('select[name="installationId"]') - .selectOption("00000000-0000-4000-8000-000000000101"); - await identityForm.locator('input[name="slackUserId"]').fill("U-SYNTHETIC"); - await identityForm - .locator('select[name="actorId"]') - .selectOption(settings.actors[0]!.id); - await identityForm - .getByRole("button", { name: "Save identity mapping" }) - .click(); - await expect(page.getByText(/U-SYNTHETIC.*→/)).toBeVisible(); - - const exposureForm = page.locator('form[aria-label="Agent exposure policy"]'); - await expect(exposureForm).toHaveCount(1); - await exposureForm - .locator('select[name="installationId"]') - .selectOption("00000000-0000-4000-8000-000000000101"); - await exposureForm - .locator('select[name="agentId"]') - .selectOption(settings.agents[0]!.id); - await exposureForm - .locator('textarea[name="allowedChannelIds"]') - .fill("C-TRIAGE, C-REVIEW"); - const enabled = exposureForm.locator('input[name="enabled"]'); - const defaultAgent = exposureForm.locator('input[name="isDefault"]'); - await enabled.uncheck(); - await expect(defaultAgent).toBeDisabled(); - await expect(defaultAgent).not.toBeChecked(); - await enabled.check(); - await defaultAgent.check(); - await exposureForm - .getByRole("button", { name: "Save exposure policy" }) - .click(); - await expect( - page.getByLabel("Configured Slack agent exposures"), - ).toContainText("default"); - await expect( - page.getByLabel("Configured Slack agent exposures"), - ).toContainText("C-TRIAGE, C-REVIEW"); - - await expect(page.getByText("dead_letter", { exact: true })).toBeVisible(); - await expect( - page.getByText("synthetic_channel_not_found", { exact: false }), - ).toBeVisible(); - const pageText = await page.locator("body").innerText(); - expect(pageText).not.toContain("xoxb-synthetic-never-render"); - expect(pageText).not.toContain("synthetic-payload-never-render"); - expect(pageText).not.toContain("encryptedBotToken"); - expect(pageText).not.toContain("encryptedPayload"); - - const revokeTrigger = page.getByRole("button", { name: "Revoke" }); - await revokeTrigger.click(); - const dialog = page.getByRole("dialog", { name: "Revoke Slack workspace?" }); - await expect(dialog).toBeVisible(); - await expect(page.getByRole("button", { name: "Cancel" })).toBeFocused(); - await page.keyboard.press("Shift+Tab"); - await expect( - page.getByRole("button", { name: "Revoke workspace" }), - ).toBeFocused(); - await page.keyboard.press("Tab"); - await expect(page.getByRole("button", { name: "Cancel" })).toBeFocused(); - const dialogAxe = await new AxeBuilder({ page }) - .include('[role="dialog"]') - .withTags(["wcag2a", "wcag2aa", "wcag21a", "wcag21aa", "wcag22aa"]) - .analyze(); - expect(dialogAxe.violations).toEqual([]); - await page.keyboard.press("Escape"); - await expect(dialog).toBeHidden(); - await expect(revokeTrigger).toBeFocused(); - - await page.getByRole("button", { name: "Reconnect Slack" }).click(); - await expect(page).toHaveURL(/oauth=synthetic/); - await page.goto("/settings/slack"); - await page.getByRole("button", { name: "Revoke" }).click(); - await page.getByRole("button", { name: "Revoke workspace" }).click(); - await expect(page.getByRole("status")).toContainText( - "Slack installation revoked.", - ); - await expect( - page.getByRole("button", { name: "Connect Slack" }), - ).toBeFocused(); - - const settingsCalls = calls.filter((call) => call.path.endsWith("/settings")); - expect(settingsCalls.length).toBeGreaterThanOrEqual(4); - expect(calls.some((call) => call.path.endsWith("/health"))).toBe(true); - expect( - calls.some( - (call) => call.method === "POST" && call.path.endsWith("/identities"), - ), - ).toBe(true); - expect( - calls.some( - (call) => call.method === "PUT" && call.path.endsWith("/exposures"), - ), - ).toBe(true); - expect( - calls.some( - (call) => call.method === "DELETE" && call.path.endsWith("/install"), - ), - ).toBe(true); - - await page.getByRole("link", { name: "kill switch" }).click(); - const restore = page.getByRole("button", { name: "Restore agent" }); - if (await restore.isVisible()) await restore.click(); - await page.getByRole("button", { name: "Kill switch" }).click(); - try { - await expect(restore).toBeVisible(); - } finally { - if (await restore.isVisible()) await restore.click(); - } - await expect(page.getByRole("button", { name: "Kill switch" })).toBeVisible(); -}); - -test("Slack administration exposes stable loading and retryable error states", async ({ - page, -}, testInfo) => { - test.skip(testInfo.project.name !== "chromium"); - let attempts = 0; - let release!: () => void; - const gate = new Promise((resolve) => { - release = resolve; - }); - await page.route("**/api/v1/slack/settings", async (route) => { - attempts += 1; - if (attempts === 1) { - await gate; - await route.fulfill({ - status: 503, - json: { detail: "Synthetic Slack diagnostics are unavailable." }, - }); - return; - } - await route.fulfill({ - status: 200, - json: { - data: { - installations: [], - actors: [], - agents: [], - identities: [], - exposures: [], - deliveries: [], - }, - }, - }); - }); - const navigation = page.goto("/settings/slack"); - await expect(page.getByRole("status")).toContainText( - "Loading Slack administration", - ); - release(); - await navigation; - const errorAlert = page - .getByRole("alert") - .filter({ hasText: "Synthetic Slack diagnostics are unavailable." }); - await expect(errorAlert).toContainText( - "Synthetic Slack diagnostics are unavailable.", - ); - await page.getByRole("button", { name: "Retry loading" }).click(); - await expect( - page.getByRole("heading", { name: "Workspace connections" }), - ).toBeVisible(); - await expect(errorAlert).toBeHidden(); -}); - -test("Slack administration reflows for mobile and 200 percent zoom without overflow", async ({ - page, - request, -}, testInfo) => { - test.skip(testInfo.project.name !== "chromium"); - const settings = await syntheticSettings(request); - await mockSlackAdministration(page, settings); - - for (const width of [390, 320]) { - await page.setViewportSize({ width, height: 900 }); - await page.goto("/settings/slack"); - await expect( - page.getByRole("heading", { name: "Slack agent harness" }), - ).toBeVisible(); - const dimensions = await page.evaluate(() => ({ - clientWidth: document.documentElement.clientWidth, - scrollWidth: document.documentElement.scrollWidth, - })); - expect(dimensions.scrollWidth, `${width}px viewport`).toBeLessThanOrEqual( - dimensions.clientWidth, - ); - } - - const accessibility = await new AxeBuilder({ page }) - .include("main") - .withTags(["wcag2a", "wcag2aa", "wcag21a", "wcag21aa", "wcag22aa"]) - .analyze(); - expect(accessibility.violations).toEqual([]); -}); From c10d435e1a8c12025b1e499451d770331b6f935f Mon Sep 17 00:00:00 2001 From: Justin Middler Date: Tue, 28 Jul 2026 17:48:24 +1000 Subject: [PATCH 4/6] docs: drop Playwright from Node 26 release checklist --- docs/operations/node-26-upgrade-deferral.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/operations/node-26-upgrade-deferral.md b/docs/operations/node-26-upgrade-deferral.md index bd94517..e4497f2 100644 --- a/docs/operations/node-26-upgrade-deferral.md +++ b/docs/operations/node-26-upgrade-deferral.md @@ -35,6 +35,5 @@ fails health checks. - [ ] `pnpm check` (lint, typecheck, test, build) - [ ] `pnpm db:migrate` / `db:bootstrap` / `db:verify-clean` as applicable - [ ] Unit + integration suites under Node 26 CI -- [ ] Playwright / Chromium e2e where required by the release - [ ] `linux/amd64` image build; web / worker / agent-gateway health - [ ] `pnpm audit --audit-level high`, licence/SBOM/container scans, CodeQL From 52f8f18d3d394c5bfd9e3b5824bd45e4c7f9dc20 Mon Sep 17 00:00:00 2001 From: Justin Middler Date: Tue, 28 Jul 2026 17:51:20 +1000 Subject: [PATCH 5/6] =?UTF-8?q?ci:=20cut=20PR=20wall-clock=20=E2=80=94=20p?= =?UTF-8?q?arallel=20jobs,=20one=20image=20build,=20Docker=20cache?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Cancel in-progress PR runs - Drop MinIO from quality (unit tests mock object storage) - Turbo local cache on GHA - Parallel quality + container (container no longer waits on quality for PRs) - Remove duplicate full image build from security workflow on PR/push (weekly schedule retains deep Trivy pass) - Dockerfile: manifest-first layers + pnpm/turbo BuildKit caches - Tighter .dockerignore for smaller build context --- .dockerignore | 36 +++++++++++++- .github/workflows/ci.yml | 88 ++++++++++++++++------------------ .github/workflows/security.yml | 8 ++++ Dockerfile | 32 +++++++++++-- turbo.json | 3 +- 5 files changed, 112 insertions(+), 55 deletions(-) diff --git a/.dockerignore b/.dockerignore index 9f6fd88..3674302 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,9 +1,43 @@ +# VCS / IDE .git -.next +.github +.claude +.hermes .turbo +.next +.playwright +.vscode +.idea + +# Dependencies & build outputs (rebuilt inside the image) node_modules +**/node_modules +**/dist +**/.next +**/coverage +**/tsconfig.tsbuildinfo + +# Tests & local tooling (not needed for production image) +tests +**/*.test.ts +**/*.spec.ts test-results playwright-report screenshots +vitest.config.ts +playwright*.config.ts + +# Docs / non-runtime content (keep skills + deploy runtime) +docs +*.md +!skills/**/SKILL.md + +# Env & secrets .env +.env.* +!.env.example + +# Logs / temp *.log +tmp +.DS_Store diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0f72d8c..936c636 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,15 +6,21 @@ on: tags: ["v*.*.*"] pull_request: +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + permissions: contents: read env: CI: "true" + TURBO_TELEMETRY_DISABLED: "1" DATABASE_URL: postgresql://muster:muster@127.0.0.1:5432/muster REDIS_URL: redis://127.0.0.1:6379 BETTER_AUTH_SECRET: muster-ci-only-secret-at-least-32-characters BETTER_AUTH_URL: http://127.0.0.1:3000 + # Unit tests stub object-storage env; no MinIO service required for quality. OBJECT_STORAGE_ENDPOINT: http://127.0.0.1:9000 OBJECT_STORAGE_REGION: us-east-1 OBJECT_STORAGE_BUCKET: muster-evidence @@ -22,6 +28,8 @@ env: OBJECT_STORAGE_SECRET_KEY: local-minio-secret jobs: + # Fast path: unit, lint, typecheck, build, migration drift, shell installers. + # Postgres only — no MinIO/Redis containers (unit tests mock or skip). quality: runs-on: ubuntu-24.04 services: @@ -35,12 +43,6 @@ jobs: options: >- --health-cmd "pg_isready -U muster -d muster" --health-interval 5s --health-timeout 3s --health-retries 20 - redis: - image: redis:8.2.1-bookworm - ports: ["6379:6379"] - options: >- - --health-cmd "redis-cli ping" - --health-interval 5s --health-timeout 3s --health-retries 20 steps: - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 - uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa # v4 @@ -50,58 +52,47 @@ jobs: with: node-version: "26" cache: pnpm - - name: Start object storage - run: | - docker run --detach --name muster-ci-minio \ - --publish 9000:9000 \ - --env MINIO_ROOT_USER=muster \ - --env MINIO_ROOT_PASSWORD=local-minio-secret \ - minio/minio:RELEASE.2025-09-07T16-13-09Z \ - server /data - for attempt in {1..20}; do - if curl --fail --silent http://127.0.0.1:9000/minio/health/live >/dev/null; then - break - fi - if [ "$attempt" -eq 20 ]; then - docker logs muster-ci-minio - exit 1 - fi - sleep 2 - done - docker run --rm --network host \ - --entrypoint /bin/sh \ - minio/mc:RELEASE.2025-08-13T08-35-41Z \ - -c 'mc alias set ci http://127.0.0.1:9000 muster local-minio-secret && - mc mb --ignore-existing ci/muster-evidence && - mc version enable ci/muster-evidence && - mc anonymous set none ci/muster-evidence' + - name: Turbo cache + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + with: + path: .turbo + key: turbo-${{ runner.os }}-node26-${{ hashFiles('pnpm-lock.yaml', 'turbo.json') }}-${{ github.sha }} + restore-keys: | + turbo-${{ runner.os }}-node26-${{ hashFiles('pnpm-lock.yaml', 'turbo.json') }}- + turbo-${{ runner.os }}-node26- - run: pnpm install --frozen-lockfile + # Pure shell tests: no install side-effects beyond repo files. - run: pnpm test:release-homelab - run: pnpm test:release-image - - name: Build database dependencies - run: pnpm exec turbo build --filter=@muster/database - - run: pnpm db:migrate - - run: pnpm db:bootstrap - - run: pnpm db:verify-clean - - run: pnpm lint - - run: pnpm typecheck - run: pnpm test:homelab-installer - - run: pnpm test:unit - - run: pnpm contracts:generate - - name: Verify committed migrations + - run: pnpm skills:validate + - run: pnpm kelpie:certify-mock + - name: Database migrate + bootstrap + clean verify + run: | + pnpm exec turbo build --filter=@muster/database + pnpm db:migrate + pnpm db:bootstrap + pnpm db:verify-clean + - name: Lint, typecheck, unit tests, build + run: | + # Single turbo invocation reuses package graph and local turbo cache. + pnpm exec turbo run lint typecheck test build --concurrency=100% + - name: Verify contracts + migrations stay committed run: | + pnpm contracts:generate pnpm db:generate - git diff --exit-code -- packages/database/migrations - - run: pnpm build + git diff --exit-code -- packages/contracts packages/database/migrations + # Secrets/audit/CodeQL only — full image rebuild lives in `container` (once). release-security: permissions: contents: read security-events: write uses: ./.github/workflows/security.yml + # Build once per workflow; do not wait for quality on PRs (wall-clock parallel). + # On main/tag, promote still waits for quality + security via the promote job. container: - needs: quality runs-on: ubuntu-24.04 outputs: image_ref: ${{ steps.image.outputs.ref }} @@ -154,8 +145,8 @@ jobs: org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }} provenance: ${{ github.event_name == 'push' && 'mode=max' || false }} sbom: ${{ github.event_name == 'push' }} - cache-from: type=gha - cache-to: type=gha,mode=max + cache-from: type=gha,scope=muster-image + cache-to: type=gha,mode=max,scope=muster-image - name: Scan built image with Trivy run: | docker run --rm \ @@ -200,7 +191,7 @@ jobs: muster-sbom.cdx.json muster-image.txt SHA256SUMS - - name: Verify container starts + - name: Verify container image exists if: github.event_name == 'pull_request' run: docker image inspect "${{ steps.image.outputs.build_ref }}" - name: Attest published image provenance @@ -230,9 +221,10 @@ jobs: docker buildx imagetools inspect "$public_image" --raw | ./scripts/verify-image-platform.sh + # Merge gate: quality + container + security must all pass before promote. promote: if: github.event_name == 'push' - needs: [container, release-security] + needs: [quality, container, release-security] runs-on: ubuntu-24.04 concurrency: group: muster-release-tags-${{ github.repository }} diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 7d407c5..62bc782 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -23,6 +23,7 @@ jobs: runs-on: ubuntu-24.04 steps: - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + # Full history only for gitleaks; shallow clone is enough for audit/licences. with: fetch-depth: 0 - uses: gitleaks/gitleaks-action@ff98106e4c7b2bc287b24eaf42907196329070c7 # v2 @@ -46,7 +47,12 @@ jobs: name: dependency-licences path: licenses.json + # Full image rebuild + Trivy used to run on every PR *and* duplicate the + # main CI container job. Keep the deep image security pass on schedule + # (and when this workflow is run on main via schedule only). PR/push image + # scan lives in ci.yml `container` (single build). image: + if: github.event_name == 'schedule' runs-on: ubuntu-24.04 steps: - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 @@ -57,6 +63,8 @@ jobs: load: true pull: true tags: muster:security + cache-from: type=gha,scope=muster-image + cache-to: type=gha,mode=max,scope=muster-image - uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 with: image-ref: muster:security diff --git a/Dockerfile b/Dockerfile index 3f88fcd..de2659c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,14 +1,32 @@ # syntax=docker/dockerfile:1.7 +# ----------------------------------------------------------------------------- +# Build stage — layer order optimised for BuildKit + GHA cache hits. +# Copy lockfiles and package manifests before sources so dependency installs +# reuse cache when only app code changes. +# ----------------------------------------------------------------------------- FROM node:26-bookworm-slim AS build -ENV PNPM_HOME=/pnpm -ENV PATH=$PNPM_HOME:$PATH +ENV PNPM_HOME=/pnpm \ + PATH=/pnpm:$PATH \ + CI=true \ + TURBO_TELEMETRY_DISABLED=1 \ + NEXT_TELEMETRY_DISABLED=1 # Node 26 official slim images no longer ship corepack on PATH by default. RUN npm install -g corepack@latest && corepack enable WORKDIR /workspace + +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml turbo.json tsconfig.json ./ +# BuildKit --parents keeps apps/foo/package.json layout for the workspace. +COPY --parents apps/*/package.json packages/*/package.json ./ + +RUN --mount=type=cache,id=pnpm,target=/pnpm/store \ + pnpm install --frozen-lockfile + COPY . . -RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile -RUN pnpm contracts:generate && pnpm build + +RUN --mount=type=cache,id=turbo,target=/workspace/.turbo \ + pnpm contracts:generate && pnpm build + RUN pnpm deploy --filter=@muster/worker --prod /prod/worker \ && pnpm deploy --filter=@muster/agent-gateway --prod /prod/agent-gateway \ && pnpm deploy --filter=@muster/database --prod /prod/database \ @@ -16,8 +34,12 @@ RUN pnpm deploy --filter=@muster/worker --prod /prod/worker \ && cp -R /workspace/apps/web/.next/static /workspace/apps/web/.next/standalone/apps/web/.next/static \ && cp -R /workspace/apps/web/public /workspace/apps/web/.next/standalone/apps/web/public +# ----------------------------------------------------------------------------- +# Runtime — distroless Node 26 +# ----------------------------------------------------------------------------- FROM gcr.io/distroless/nodejs26-debian13:nonroot AS runtime -ENV NODE_ENV=production +ENV NODE_ENV=production \ + NEXT_TELEMETRY_DISABLED=1 WORKDIR /app COPY --from=build --chown=nonroot:nonroot /workspace/apps/web/.next/standalone ./web COPY --from=build --chown=nonroot:nonroot /prod/worker ./worker diff --git a/turbo.json b/turbo.json index 8e99201..30379fc 100644 --- a/turbo.json +++ b/turbo.json @@ -1,10 +1,11 @@ { "$schema": "https://turbo.build/schema.json", "ui": "tui", + "globalPassThroughEnv": ["CI", "DATABASE_URL", "REDIS_URL", "NODE_ENV"], "tasks": { "build": { "dependsOn": ["^build"], - "outputs": [".next/**", "dist/**", "generated/**"] + "outputs": [".next/**", "!.next/cache/**", "dist/**", "generated/**"] }, "dev": { "cache": false, From 2118ddc7bfe510a87fc7ceae671515b51a9581b2 Mon Sep 17 00:00:00 2001 From: Justin Middler Date: Tue, 28 Jul 2026 17:55:35 +1000 Subject: [PATCH 6/6] fix(docker): drop unsupported COPY --parents for BuildKit 1.7 GHA buildx rejects --parents on dockerfile:1.7. Use explicit per-package manifest COPY lines so the lockfile-first layer cache still works. --- Dockerfile | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index de2659c..50277e5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,8 +16,31 @@ RUN npm install -g corepack@latest && corepack enable WORKDIR /workspace COPY package.json pnpm-lock.yaml pnpm-workspace.yaml turbo.json tsconfig.json ./ -# BuildKit --parents keeps apps/foo/package.json layout for the workspace. -COPY --parents apps/*/package.json packages/*/package.json ./ +COPY apps/agent-gateway/package.json apps/agent-gateway/ +COPY apps/mcp-server/package.json apps/mcp-server/ +COPY apps/web/package.json apps/web/ +COPY apps/worker/package.json apps/worker/ +COPY packages/agent-harness/package.json packages/agent-harness/ +COPY packages/agents/package.json packages/agents/ +COPY packages/alerts/package.json packages/alerts/ +COPY packages/api-client/package.json packages/api-client/ +COPY packages/audit/package.json packages/audit/ +COPY packages/auth/package.json packages/auth/ +COPY packages/authz/package.json packages/authz/ +COPY packages/config/package.json packages/config/ +COPY packages/contracts/package.json packages/contracts/ +COPY packages/database/package.json packages/database/ +COPY packages/event-protocol/package.json packages/event-protocol/ +COPY packages/evidence/package.json packages/evidence/ +COPY packages/integrations/package.json packages/integrations/ +COPY packages/investigations/package.json packages/investigations/ +COPY packages/mcp/package.json packages/mcp/ +COPY packages/notifications/package.json packages/notifications/ +COPY packages/rooms/package.json packages/rooms/ +COPY packages/search/package.json packages/search/ +COPY packages/test-utils/package.json packages/test-utils/ +COPY packages/ui/package.json packages/ui/ +COPY packages/workflows/package.json packages/workflows/ RUN --mount=type=cache,id=pnpm,target=/pnpm/store \ pnpm install --frozen-lockfile