diff --git a/.github/scripts/tests/post-merge-runner-routing.test.mjs b/.github/scripts/tests/post-merge-runner-routing.test.mjs index 95c2a1c5ef..68a2cada62 100644 --- a/.github/scripts/tests/post-merge-runner-routing.test.mjs +++ b/.github/scripts/tests/post-merge-runner-routing.test.mjs @@ -10,7 +10,7 @@ const base = { ref: "refs/heads/master", event_name: "push", sha, }; const expectedJobs = { - "cloud-readiness.yml": ["artifacts", "source_verified", "ready"], + "cloud-readiness.yml": [], "cloud-artifacts.yml": ["dispatch_migrator"], "release-verify.yml": ["typecheck", "general_tests", "serialized_tests", "runner_workflow_evals", "verify_paperclip_runner", "build"], "runner-chaos-evals.yml": ["chaos_and_recovery"], @@ -81,3 +81,27 @@ for (const [file, expectedNames] of Object.entries(expectedJobs)) { }); } } + + +test("Cloud readiness bookkeeping never waits for the AWS verification fleet", () => { + const workflow = readFileSync(new URL("../../workflows/cloud-readiness.yml", import.meta.url), "utf8"); + const bodies = new Map(); + for (const [name, needs] of [ + ["artifacts", null], + ["source_verified", "[verify]"], + ["ready", "[verify, image, artifacts]"], + ]) { + const body = workflow.match(new RegExp(`^ ${name}:\\n([\\s\\S]*?)(?=^ [a-z_]+:|(?![\\s\\S]))`, "m"))?.[1]; + assert.ok(body, `missing ${name} job`); + bodies.set(name, body); + assert.match(body, /^ runs-on: ubuntu-latest$/m); + assert.doesNotMatch(body, /^ +continue-on-error:|^ +if:.*always\(\)/m); + assert.match(body, /^ if: github.repository == 'paperclipai\/paperclip' && github.ref == 'refs\/heads\/master'$/m); + assert.match(body, /^ +SOURCE_SHA: \$\{\{ github.sha \}\}$/m); + assert.equal(body.match(/^ needs: (.+)$/m)?.[1] ?? null, needs, `${name} prerequisites`); + } + assert.match(bodies.get("artifacts"), /^ run: node scripts\/cloud-readiness.mjs "\$SOURCE_SHA"$/m); + assert.match(bodies.get("source_verified"), /^ run: node --test scripts\/cloud-source-verification.test.mjs$/m); + assert.match(bodies.get("source_verified"), /echo "Cloud source verified v1: \$SOURCE_SHA"/); + assert.match(bodies.get("ready"), /echo "Cloud deployable v1: \$SOURCE_SHA"/); +}); diff --git a/.github/workflows/cloud-readiness.yml b/.github/workflows/cloud-readiness.yml index 002da29614..453cad2172 100644 --- a/.github/workflows/cloud-readiness.yml +++ b/.github/workflows/cloud-readiness.yml @@ -32,7 +32,8 @@ jobs: artifacts: if: github.repository == 'paperclipai/paperclip' && github.ref == 'refs/heads/master' name: Wait for exact-source cloud artifacts - runs-on: ${{ vars.AWS_POST_MERGE_CI_ENABLED == 'true' && github.repository == 'paperclipai/paperclip' && github.repository_id == '1170821064' && github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && 'runs-on/fleet=paperclip-post-merge-x64/env=public-ci' || 'ubuntu-latest' }} + # Bookkeeping must not wait for the AWS builders it observes. + runs-on: ubuntu-latest timeout-minutes: 35 permissions: contents: read @@ -55,7 +56,8 @@ jobs: name: Cloud source verified v1 needs: [verify] if: github.repository == 'paperclipai/paperclip' && github.ref == 'refs/heads/master' - runs-on: ${{ vars.AWS_POST_MERGE_CI_ENABLED == 'true' && github.repository == 'paperclipai/paperclip' && github.repository_id == '1170821064' && github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && 'runs-on/fleet=paperclip-post-merge-x64/env=public-ci' || 'ubuntu-latest' }} + # Bookkeeping must not wait for the AWS builders it observes. + runs-on: ubuntu-latest timeout-minutes: 5 permissions: contents: read @@ -81,7 +83,8 @@ jobs: name: Cloud deployable v1 needs: [verify, image, artifacts] if: github.repository == 'paperclipai/paperclip' && github.ref == 'refs/heads/master' - runs-on: ${{ vars.AWS_POST_MERGE_CI_ENABLED == 'true' && github.repository == 'paperclipai/paperclip' && github.repository_id == '1170821064' && github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && 'runs-on/fleet=paperclip-post-merge-x64/env=public-ci' || 'ubuntu-latest' }} + # Bookkeeping must not wait for the AWS builders it observes. + runs-on: ubuntu-latest timeout-minutes: 5 steps: - name: Record cloud readiness diff --git a/.github/workflows/docker-cloud.yml b/.github/workflows/docker-cloud.yml index 38fb18e2f7..585c64a5a9 100644 --- a/.github/workflows/docker-cloud.yml +++ b/.github/workflows/docker-cloud.yml @@ -72,24 +72,6 @@ jobs: id: tools-epoch run: echo "epoch=$(date -u +%G-W%V)" >> "$GITHUB_OUTPUT" - # Each SHA exports its own cache. Import recent first-parent caches so - # a late older build cannot overwrite a newer build's cache manifest. - # The legacy ref keeps the first builds warm during the transition. - - name: Select cloud cache ancestry - id: cloud-cache - env: - CACHE_IMAGE: ghcr.io/${{ github.repository }} - run: | - set -euo pipefail - { - echo 'sources<> "$GITHUB_OUTPUT" - - name: Setup pnpm uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6 with: @@ -175,6 +157,14 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4 + # Mixing several historical manifests missed otherwise reusable native + # layers on fresh builders. Import the nearest available complete cache. + - name: Select cloud cache ancestry + id: cloud-cache + env: + CACHE_IMAGE: ghcr.io/${{ github.repository }} + run: node scripts/select-cloud-cache.mjs + # Deployment tooling reads these labels from the registry to verify an # image's schema expectations against a migrator before deploying it, # without pulling the image. The server refuses to start when the @@ -239,7 +229,7 @@ jobs: # Same-SHA builds serialize above; different SHAs never share a # writable cache ref. Registry layers are content-addressed and # shared even when cache manifests have separate tags. - cache-from: ${{ steps.cloud-cache.outputs.sources }} + cache-from: ${{ steps.cloud-cache.outputs.source }} cache-to: type=registry,ref=ghcr.io/${{ github.repository }}:buildcache-cloud-${{ github.sha }},mode=max tags: ${{ steps.meta-cloud.outputs.tags }} labels: ${{ steps.meta-cloud.outputs.labels }} diff --git a/.github/workflows/docker-runner-check.yml b/.github/workflows/docker-runner-check.yml index 7c3b7820b5..d595522292 100644 --- a/.github/workflows/docker-runner-check.yml +++ b/.github/workflows/docker-runner-check.yml @@ -32,7 +32,7 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4 # Compile the real target, then change source in a disposable context. - # Require dependency-layer reuse and changed metadata from the real binary. + # A fresh builder must import dependencies and produce changed binary metadata. # No registry credentials, external cache, or image publication. - name: Verify native build and dependency cache reuse run: bash scripts/check-docker-runner-cache.sh diff --git a/doc/DEVELOPING.md b/doc/DEVELOPING.md index 82ae095bc5..79d886f834 100644 --- a/doc/DEVELOPING.md +++ b/doc/DEVELOPING.md @@ -352,6 +352,19 @@ These browser suites are intended for targeted local verification and CI, not th For normal issue work, start with the smallest targeted check that proves the change. Reserve repo-wide typecheck/build/test runs for PR-ready handoff or changes broad enough that narrow checks do not cover the risk. +### Task search evaluation + +The task search relevance rubric and regression corpus are documented in +[SEARCH.md](SEARCH.md). Run the real PostgreSQL relevance suite with: + +```sh +pnpm exec vitest run server/src/__tests__/task-search-quality.test.ts +``` + +Set `SEARCH_EVAL_SCALE=1` to additionally measure a disposable 10,000-task, +30,000-comment dataset. `SEARCH_EVAL_REPORT=/tmp/search-quality.json` saves +per-query results and latency measurements; scale measurements are opt-in. + ### Recent task ordering The streamlined sidebar keeps five recent tasks per company and account in browser diff --git a/doc/DOCKER.md b/doc/DOCKER.md index 2905bfe957..04f23424b4 100644 --- a/doc/DOCKER.md +++ b/doc/DOCKER.md @@ -357,6 +357,14 @@ own native build; no cross-architecture binary is reused. No additional GitHub Actions cache is created. A cold build also installs the recipe generator and compiles dependencies, so the savings apply after those layers are available. +Cloud builds import one registry cache: the first available full-SHA cache in +the current commit's ten-entry first-parent ancestry, with the legacy cache +as a final fallback. Each build still exports its own SHA cache with +`mode=max`. In fresh-builder checks, importing several historical manifests +missed native layers that a single matching manifest reused. The selector +inspects metadata after Docker login, stops at the first available cache, and +permits a cold build if no cache can be read. + The application build inherits that stage and still runs the normal server build, including Cargo, binary staging, and generated-contract checks. Rust input file times are normalized in both stages so fresh checkouts do not force @@ -367,12 +375,14 @@ directory as before. Cache misses only cost compilation time. Pull requests that change the Dockerfile, Docker ignore rules, or Runner native inputs also build the isolated `runner-build` target in `Docker Runner check`. The check runs `bash scripts/check-docker-runner-cache.sh` against a disposable -copy of tracked source and the actual Docker ignore rules. It compiles a baseline, -changes a Rust metadata constant, and rebuilds. It requires a cached dependency -build, an unchanged dependency recipe, and changed metadata from the real binary. -It also verifies that a dependency declaration change alters the recipe. The -probe exports only small metadata files, avoiding a large image import into the -Docker daemon. It catches missing embedded inputs before the post-merge build. -It uses a GitHub-hosted runner with read-only repository access and does not -publish images or cache artifacts. Allow up to 20 minutes for its cold build and +copy of tracked source and the actual Docker ignore rules. It compiles a baseline +and exports a local cache, removes that builder, changes a Rust metadata constant, +and rebuilds on a fresh builder using only the exported cache. It requires a +cached dependency build, an unchanged dependency recipe, and changed metadata +from the real binary. It also verifies that a dependency declaration change +alters the recipe. The probe exports small metadata results instead of importing +a large test image into the Docker daemon. Temporary builders and cache files +are removed afterward. It catches missing embedded inputs before the post-merge +build. It uses a GitHub-hosted runner with read-only repository access and never +publishes images or registry caches. Allow up to 20 minutes for its cold build and source rebuild. diff --git a/doc/PRODUCT.md b/doc/PRODUCT.md index 3a98e41b1c..6b2dc4d3f3 100644 --- a/doc/PRODUCT.md +++ b/doc/PRODUCT.md @@ -161,6 +161,24 @@ Paperclip’s core identity is a **control plane for autonomous AI companies**, 9. **Thin core, rich edges** Put optional chat, knowledge, and special surfaces into plugins/extensions rather than bloating the control plane. +### Experimental iMessage Photon channel + +A Photon Cloud project can represent one agent through the existing +experimental channel subsystem. DMs and explicitly enabled groups create or +continue task-bound conversations. Linked sender identity is the default; +telephone numbers, email addresses, names, and group membership do not grant +Paperclip authority. Photos/files and ordinary questions/confirmations use the +existing attachment, interaction, continuation, and publication contracts. +Pause and Disconnect govern runtime behavior independently of the UI gate. +Local Mac access, unsolicited conversations, and SMS/RCS +fallback are excluded. Live qualification is required before release readiness. +Pro shared allocation supports DMs only, with sender enrollment in Photon and +separate identity linking in Paperclip. Shared channels reserve one project, not +a pool phone number; group admission and publication are disabled. Dedicated +allocation retains one selected number and individually enabled groups. + +See [iMessage Photon](connections/IMESSAGE-PHOTON.md) for the implementation +contract, setup, recovery, boundaries, and qualification status. ### Experimental persistent agent conversations Agent Chat is an opt-in core task presentation (`enableAgentChat`, off by default). Each person has one persistent task-backed conversation per agent and company, with ordinary company task visibility. The shared task composer, transcript, tools, files, and document panel remain the interaction surface. Agents clarify goals and hand substantial execution to linked, assigned tasks; a reply ends a turn without completing the conversation. `/new` starts fresh provider context in the same conversation while preserving visible history and artifacts. Healthy idle conversations wait for a message and do not count as unfinished execution work. See `doc/plans/2026-09-10-agent-chat.md` for the implementation contract. diff --git a/doc/RELEASE-AUTOMATION-SETUP.md b/doc/RELEASE-AUTOMATION-SETUP.md index 4817e5d7fc..5e5afc481d 100644 --- a/doc/RELEASE-AUTOMATION-SETUP.md +++ b/doc/RELEASE-AUTOMATION-SETUP.md @@ -399,3 +399,18 @@ fixture setup; it does not make a single test faster. The file-duration manifest also records the native Codex Runner integration suite's measured import and execution cost, so the existing file balancer accounts for it in both ordinary PR and release verification. + + +## Cloud readiness runner placement + +When AWS routing is enabled, Cloud image builds use `paperclip-cloud-build-x64` +and source verification uses `paperclip-post-merge-x64`. The artifact wait and +the `Cloud source verified v1` and `Cloud deployable v1` marker jobs run on +GitHub-hosted runners. These small jobs must not hold or wait for capacity in +the source-verification fleet. During a merge +burst, even a completed build must wait for its marker before consumers can +recognize readiness. + +Runner placement does not change readiness requirements: exact-source artifacts, +all source checks, and the image verification must still pass. The versioned +markers and their dependency gates are unchanged. diff --git a/doc/SEARCH.md b/doc/SEARCH.md new file mode 100644 index 0000000000..bdda5ce317 --- /dev/null +++ b/doc/SEARCH.md @@ -0,0 +1,200 @@ +# Task search relevance + +## Product rubric + +Search should help someone reopen work they remember, using whatever fragment +stuck in memory: an ID, a few title words, a technical name, or something in the +conversation. The first screen should contain plausible answers, with enough +context to explain each match. + +| Intent | Good result | Failure | +|---|---|---| +| Known task ID | Exact ID first, case-insensitive; accept `PAP-42`, `pap42`, `PAP 42` | A mention or neighboring ID beats the task | +| Remembered title | Exact title, phrase, then all title words in any order | A recent comment mentioning those words beats the title | +| Several concepts | Every meaningful query term contributes, including short terms such as API/UI | A task matches only one common word | +| Exact phrase | Quoted text stays together and literal | Quotes silently behave like OR or fuzzy search | +| Thread memory | Find words across task text, comments and current documents | Relevant content exists but the task cannot be found | +| Technical text | Preserve underscores, percent signs, paths and numbers | SQL wildcard expansion or fuzzy IDs return unrelated work | +| Typo | Conservative title-word correction; all other terms still required | Ignoring a short term changes the query's meaning | +| Result explanation | Show the best evidence and link to its source | A title hit jumps into an unrelated comment | +| Old work | Strong completed-task matches remain ahead of weak recent hits | Recency/activity replaces relevance | +| Boundaries | Company, visibility, deletion and explicit filters always apply | Content leaks through counts, snippets or typo matches | +| Operations | PostgreSQL only, synchronous current-row reads, bounded query/page sizes | A worker, remote index or eventual-consistency repair is required | + +Judge results on a 0–3 scale: **3** directly answers the remembered task intent, +**2** is useful related work, **1** is only an incidental mention, **0** is +irrelevant. Ambiguous short queries may have several grade-3 answers; do not +invent a unique intended task for them. + +Acceptance gates: + +- Every unambiguous known-task case returns its intended task first. +- Every grade-3 result in the small judged corpus appears in the first five. +- All explicit negative, visibility, filter, freshness and literal-query cases pass. +- Report mean reciprocal rank (first grade-3 result) and nDCG@5 (graded ordering + and recall). Target MRR ≥ 0.95 and nDCG@5 ≥ 0.90 on the authored corpus. +- Measure both the full search page and the command-palette/task-list API. +- Measure database-backed latency separately from relevance. Report dataset + size, warm/cold assumptions and hardware; a small fixture is not scale proof. + Initial target: warm p95 ≤ 250 ms at 10,000 tasks and 30,000 short comments. + A regression greater than 20% from baseline requires investigation and an + explicit explanation of the cost; do not describe a quality improvement as + latency-neutral when it is not. + +The initial corpus is synthetic and deliberately adversarial. It includes +plausible distractors and gives older completed tasks strong relevance labels. +It is not evidence that every real user's search is solved. Add real failed +queries and human judgments as they become available. Do not adjust judgments +just to improve a score. + +## Previous behavior + +The command palette calls the issue-list endpoint. It searched one literal +substring across title, identifier, description and comments, prioritized titles +before identifiers, and did not search documents or recover typos. Reordered +words commonly returned no result. + +Company search used a different algorithm: any token admitted a result, bonuses +from titles, comments and documents accumulated, and title-only token coverage +was indistinguishable from words scattered across a long thread. It ran edit +distance for title words, discarded short terms from fuzzy matching, and also +fuzzed identifiers. Quotes were tokenized but did not constrain other matches. + +## Matching contract + +Both task search paths use `server/src/services/task-search.ts`. Search is lexical: +trim/collapse whitespace, normalize case, keep quoted phrases, remove a small +set of unquoted grammatical filler words, deduplicate terms, and retain up to +8 terms within the existing 200-character query bound. All-filler queries keep +their terms. No synonym service, embedding model or language-specific stemming +is involved. + +All retained terms must match. Full search and task lists allow terms to occur +across task text and current, undeleted conversation/document content. The +Tasks scope requires coverage in task text. Comments and Documents require a +participating match in that source, while retaining the task context. Exact/prefix +identifiers and conservative title-word typo matches are additional task matches. +Typo matching runs only when no literal match satisfies the requested filters. +It never guesses task numbers, loosens a quoted phrase or drops a short query +term. Alphabetic terms of one to three characters must begin a word, so `UI` +does not match `build`, while incomplete longer words still support typeahead. + +Ranking uses disjoint bands: exact ID, ID prefix, exact title, title phrase, +all title terms, all task-text terms, all thread terms, then title typo recovery. +Whole-word title matches and title prefixes break close ties; status has only a +small effect within a band. Full search uses recency and stable IDs for remaining +ties; task lists retain their existing priority/activity tie-breaking. Explicit +created/updated/priority sort modes retain their documented behavior. Other +entity types retain their existing scoring rules, rescaled to keep exact names +ahead of speculative task typo matches. The UI displays the server's order +without regrouping results by source. + +The existing `pg_trgm` indexes support literal substring retrieval. Tagged +comment/document match sets are computed once per search with separate indexed +patterns. Ranking stages carry compact flags; descriptions and matching snippets +are fetched for the result window. The database reads current rows, so creates, edits, deletions and +hidden-task changes take effect without indexing jobs. Bounded edit-distance +checks operate on titles only, run only as a zero-result fallback, and guard +fuzzystrmatch's 255-character argument limit. There is no schema migration or +new extension in this change. + +PostgreSQL documents the existing index support in +[pg_trgm](https://www.postgresql.org/docs/17/pgtrgm.html). + +## Reproduce the evaluation + +```sh +pnpm exec vitest run server/src/__tests__/task-search-quality.test.ts +# Also write per-query rankings and metrics for inspection: +SEARCH_EVAL_REPORT=/tmp/search-quality.json pnpm exec vitest run server/src/__tests__/task-search-quality.test.ts +# Include the larger latency dataset and query plans: +SEARCH_EVAL_SCALE=1 SEARCH_EVAL_REPORT=/tmp/search-scale.json pnpm exec vitest run server/src/__tests__/task-search-quality.test.ts +``` + +The fixture is `server/src/__tests__/fixtures/task-search-corpus.ts`. Tests run +the real services against a temporary embedded PostgreSQL database with the +normal migrations. `SEARCH_EVAL_BASELINE=1` records judgments without asserting +improved behavior. To compare another revision, copy this test, its fixture and +`task-search.ts` into a separate worktree for that revision, leave its actual +`company-search.ts` and `issues.ts` services unchanged, and run with +`SEARCH_EVAL_BASELINE=1`. The copied helper is not used by the baseline services; +its query-plan branch is disabled in baseline mode. + +## Initial evaluation — 2026-09-12 + +Compared against `2083bf6f9` using the same 31-task corpus and 24 queries (23 +queries with intended answers, plus one no-result query). + +| Surface | Intended answer first, before → after | MRR, before → after | nDCG@5, before → after | +|---|---|---|---| +| Full search | 17/23 → 23/23 | 0.828 → 1.000 | 0.904 → 0.999 | +| Quick search / task list | 5/23 → 23/23 | 0.268 → 1.000 | 0.339 → 0.999 | + +The relevance gates pass. These results measure the authored corpus, not general +search accuracy. The no-result query also returns no tasks in both surfaces. + +The scale run adds 10,000 tasks with ~300-character descriptions and 30,000 +~345-character comments. Measurements call the real service methods (including +facets/snippets or task-list hydration), excluding HTTP and UI debounce. Each +query has one separately recorded first request and 20 warm repetitions; p95 +is the 19th sorted warm sample. This is not a cold-disk test. Both revisions used +PostgreSQL 18.1, default planner/memory settings, and `ANALYZE` after seeding. +The host was an Apple M5 Max with 128 GiB RAM, running an x86_64 PostgreSQL +binary and other development tests concurrently. Treat timing deltas as local +measurements, not production capacity or a controlled concurrency benchmark. + +| Query | Full p95 before → after (ms) | Quick p95 before → after (ms) | +|---|---|---| +| `GitHub OAuth` | 139 → 95 | 39 → 128 | +| `OAuth callback GitHub` | 209 → 81 | 26 → 134 | +| `mibile api` | 153 → 131 | 19 → 111 | +| `search` | 172 → 41 | 22 → 67 | +| `quasarxylophone` | 154 → 105 | 18 → 218 | +| `routine` (matches all 10,000 added tasks) | 239 → 253 | 152 → 371 | + +Selective full searches improved. Quick search is more expensive: it now +evaluates term coverage, searches documents, and can scan company titles for +typo recovery. The old quick search returned no answers for the reordered and +typo queries, so its lower cost did not deliver equivalent results. The relative +regression threshold is triggered, and broad-query p95 does **not** meet the +initial 250 ms target. This is an explicit performance limitation of this pass. +The implementation adds no operational service, but it is not latency-neutral. + +`EXPLAIN (ANALYZE, BUFFERS)` confirmed existing trigram indexes on selective +comment/document retrieval and zero fuzzy-branch executions for successful +literal searches. Removing descriptions from intermediate materialized rows +eliminated 1,699 temporary blocks (~13 MiB) of writes in the broad-query core +plan; its final measured execution was 139 ms with no temporary writes. The +quick-search endpoint also performs its existing activity sorting and task +hydration. Larger companies, long threads and sustained concurrent searches +still need production-shaped measurement before a stronger latency claim. + +Browser acceptance used the real built app against an isolated PostgreSQL +database containing this corpus. Starting from the dashboard, the Search link +found an older completed task from reordered title words and opened that task. +Command-K ranked `PAP-42` first for `pap42`; `mibile api` recovered only the +intended mobile API task and carried the query into full search. Quoted +`"connection timeout"` excluded scattered words. `Hermes parser` showed the +document title as evidence and opened the correct plan in the task's side panel. +The desktop result layout was visually inspected. Mobile layout, continuous +transition timing and production data were not part of this walkthrough. + +For a future failed search, record the query, what the person remembered, and +the intended task IDs. Grade the old top five and any missed intended tasks +before changing the ranker, add realistic distractors, then run both entry +points. Keep these judgments independent of the ranking constants. + +Verification: 78 search/parser tests (including the real PostgreSQL scale run), +14 existing task-list search/filter tests, and 29 Search/CommandPalette UI tests +passed. Workspace typecheck, the final server typecheck, production build, +Storybook build and token gates passed. The full repository test run was stopped +after `chat-channels.integration.test.ts` reported one failure in “publishes a +closed-choice question, settles its Slack card, and delivers its exact +continuation response”; that test passed when rerun alone. The remaining broad +suite was not completed, so this is not a claim of a green repository-wide run. + +The API response contracts and company authorization stay unchanged. Artifact, +agent and project ranking are separate from the task relevance rubric. Extraction +search and the specialized blocked-attention queue retain their existing +literal matching. Pure semantic paraphrases and +language-specific word inflections are outside this first lexical rubric. diff --git a/doc/SPEC-implementation.md b/doc/SPEC-implementation.md index 8502540d7a..c8da91c157 100644 --- a/doc/SPEC-implementation.md +++ b/doc/SPEC-implementation.md @@ -1148,6 +1148,9 @@ The current app also exposes V1-supporting surfaces for: - company-scoped summary slots for projects, the workspaces overview, project workspaces, and individual execution workspaces; execution-workspace slots are keyed by execution workspace id so a new workspace never inherits another workspace's summary - issue thread interactions (`suggest_tasks`, `ask_user_questions`, `request_confirmation`, `request_checkbox_confirmation`, `request_item_verdicts`) with the open-default resolver contract in §9.8.1 - issue approvals, issue references/search, labels, read state, inbox/archive state, and work products +- task search uses shared PostgreSQL matching/ranking for company search and task-list quick search; + all query terms contribute, quoted phrases stay literal, exact identifiers and direct title matches + lead relevance ordering, and the UI preserves server result order (see `doc/SEARCH.md`) - company search through `GET /companies/:companyId/search` plus agent-oriented bulk extraction through `GET /companies/:companyId/search/extract`; extraction accepts a server-escaped literal `contains`, optional server-owned URL expansion, issue/comment/document scopes, status/date filters, issue-level pagination, a @@ -1610,6 +1613,16 @@ retry budget. Existing pause, approval, budget, ownership, and dependency gates remain in effect. See `doc/execution-semantics.md` for admission and stop-proof requirements. +### Managed AI authentication + +AI credentials can be adopted into the existing Connections system. A typed +`runtimeConfig.aiConnection` selects the responsible user’s personal default, an +explicit shared grant. The existing human-audience and agent-access permissions +apply; AI credentials have no separate agent-delegation exception. Selection preserves +harness/model routing and fails closed without ambient credential fallback. +Legacy agents retain their authentication until validated adoption. See +[AI Connections](connections/AI-CONNECTIONS.md) for company isolation, compatible +methods, lifecycle, runtime enforcement, and migration details. ### Experimental task-bound email AgentMail channel connections extend the experimental conversation/task pipeline @@ -1623,6 +1636,30 @@ outcomes without a separate email composer. See [AgentMail connections](connections/AGENTMAIL.md) for setup, transports, recovery, authorization, and the API/CLI contract. +### Experimental iMessage Photon channel + +A Photon Cloud project can represent one agent through the existing +experimental channel subsystem. DMs and explicitly enabled groups create or +continue task-bound conversations. Linked sender identity is the default; +telephone numbers, email addresses, names, and group membership do not grant +Paperclip authority. Photos/files and ordinary questions/confirmations use the +existing attachment, interaction, continuation, and publication contracts. +Pause and Disconnect govern runtime behavior independently of the UI gate. +Local Mac access, unsolicited conversations, and SMS/RCS +fallback are excluded. Live qualification is required before release readiness. +Pro shared allocation supports DMs only, with sender enrollment in Photon and +separate identity linking in Paperclip. Shared channels reserve one project, not +a pool phone number; group admission and publication are disabled. Dedicated +allocation retains one selected number and individually enabled groups. + +iMessage task completion ends a turn, not its conversation. Subsequent messages +reopen the same task, including after restart; only explicit `/new` or `/close` +allows the next message to start another task. The open task receives committed +inbound comments live, with “Sent from iMessage” attribution on user bubbles. + +See [iMessage Photon](connections/IMESSAGE-PHOTON.md) for the implementation +contract, setup, recovery, boundaries, and qualification status. + ### Native task completion For ordinary low-risk tasks, accept the current agent's structured `done` claim diff --git a/doc/SPEC.md b/doc/SPEC.md index 18b1ba351f..967cd50145 100644 --- a/doc/SPEC.md +++ b/doc/SPEC.md @@ -567,3 +567,31 @@ A paused task takes over the composer with an amber notice and a Resume action. Operators must release the effective task or ancestor pause before sending a new message. The draft stays intact. This applies to both task interfaces and to board comment API requests; an agent may still report interrupted work. + +### Experimental iMessage Photon channel + +A Photon Cloud project can represent one agent through the existing +experimental channel subsystem. DMs and explicitly enabled groups create or +continue task-bound conversations. Linked sender identity is the default; +telephone numbers, email addresses, names, and group membership do not grant +Paperclip authority. Photos/files and ordinary questions/confirmations use the +existing attachment, interaction, continuation, and publication contracts. +Pause and Disconnect govern runtime behavior independently of the UI gate. +Local Mac access, unsolicited conversations, and SMS/RCS +fallback are excluded. Live qualification is required before release readiness. +Pro shared allocation supports DMs only, with sender enrollment in Photon and +separate identity linking in Paperclip. Shared channels reserve one project, not +a pool phone number; group admission and publication are disabled. Dedicated +allocation retains one selected number and individually enabled groups. + +See [iMessage Photon](connections/IMESSAGE-PHOTON.md) for the implementation +contract, setup, recovery, boundaries, and qualification status. + +## Task search relevance + +Task discovery uses PostgreSQL and the existing search indexes, with no external +search service or background indexing job. The task-list quick search and full +company search share lexical matching and ranking. Known identifiers and direct +title matches lead; current conversation and document content supplies supporting +evidence. See [Task search relevance](SEARCH.md) for the evaluation rubric, +matching contract and reproducible quality tests. diff --git a/doc/connections/AI-CONNECTIONS.md b/doc/connections/AI-CONNECTIONS.md new file mode 100644 index 0000000000..3cefc7aaad --- /dev/null +++ b/doc/connections/AI-CONNECTIONS.md @@ -0,0 +1,231 @@ +# AI Connections + +AI accounts use the existing Apps/Connections substrate. Manage them at +`/:company/apps`; select their use beside an agent's harness/model settings. +Onboarding, new-agent setup, account creation/reconnect, and inline task requests +reuse `AdapterLoginPanel`, its existing login controllers, and `AdapterLoginChrome`. +Onboarding and new-agent setup retain upstream's `SavedProviderKeySelect` and +`useSavedProviderKeys`, including saved-key references and account-specific Codex +homes. Managed default/shared accounts are additional choices in that same +selector. Selecting “Sign in to another account” survives background refreshes; +Claude authorization paste keeps upstream's immediate Connecting feedback. + +Storybook's simulated controllers and page annotations do not run in the app. + +## Compatibility and selection + +The shared `AI_CONNECTION_CAPABILITIES` contract defines these combinations: + +| Provider | Sign-in method | Existing harness | +| --- | --- | --- | +| Claude / Anthropic | Claude subscription token or Anthropic API key | Claude | +| OpenAI | ChatGPT/Codex subscription or OpenAI API key | Codex | +| OpenRouter | API key | OpenCode, with an `openrouter/` model | +| Grok / xAI | Grok subscription or xAI API key | Grok | + +Native runner supports the corresponding existing Codex, OpenCode, and Claude +ACP profiles. Connections creation and reconnect mount `AgentProviderConnection`, +the same provider tiles, method controls, API entry, and `AdapterLoginPanel` used +by agent setup. Supported sandbox environments use onboarding's existing browser +sign-in controllers. Self-hosted installations use the shared terminal sign-in +instructions described below and require no sandbox. Environment selection does +not change agent execution settings. +API keys are validated against fixed provider endpoints; redirects +and caller-supplied validation URLs are rejected. + +`runtimeConfig.aiConnection` contains `provider`, `method`, and `mode`: + +- `responsible_user`: resolve the run's responsible user's personal default. +- `shared`: use the named `connectionId` and `grantId`, with audience and agent + access checks. +- `delegated`: retained only to read legacy bindings. It cannot bypass human + access; a personal credential remains available only for its owner's tasks. + New configuration offers personal defaults or shared accounts. + +“Which humans can use this credential?” is the sole permission for whose work +can use the account. “Just me” means the personal owner; shared accounts allow +selected company members or every company member. The separate agent-access +setting determines which agents can use it. There is no additional AI agent +authorization, and old delegation records do not override the human audience. + +A connection choice never changes the harness, model, or provider routing. +Changing those separately may make a binding incompatible; saving then requires +a compatible choice. Agent configuration cannot grant access to another account. + +Personal defaults are unique per company, user, provider, and sign-in method. +The first successful personal connection sets a default only when none exists. +Revocation retains the unavailable default; connecting another account does not +silently replace it. Change it explicitly on the account detail page. + +## Storage and API + +AI connections pair `connectionPurpose: ai` with `transport: runtime_auth`. +Database checks and the shared discriminator enforce the pair. These entries +cannot participate in tool discovery, MCP gateways, execution, or channels. +Anthropic retains its existing tool methods alongside its AI methods. Catalog +validation also pairs AI metadata with runtime authentication and rejects unsupported +sign-in methods. Provider artwork and source provenance live in +`ui/public/brands/apps/manifest.json`; OpenRouter uses its official sign-in assets, +and OpenAI/Grok reuse the repository's pinned Lobe Icons source and license. + +Provider/method metadata lives in `config.ai`. Credentials live on the existing +grant through encrypted vault secret references, with existing consumer bindings. +Safe provider-reported account identity is optional; secret references, tokens, +and authentication paths are never account labels. + +Company-scoped `/api/companies/:companyId/ai-connections` operations provide list, +API-key creation/reconnect, personal defaults, completed login references, and +active-run attribution. Existing Connections operations handle naming, access, +and revocation. Mutation authorization is enforced server-side. OpenAPI documents the new board-only +operations. Agent-originated configuration and environment tests resolve the +authenticated request’s responsible user; an agent ID is never a personal-account +owner. A missing responsible identity blocks personal-default resolution. + +Subscription login attempts retain their company, owner, method, access intent, +and reconnect target in the existing durable authentication session. Duplicate +completion returns the same connection/grant. Abandoned or expired attempts cannot +save a healthy connection. Reconnect preserves the connection ID, bindings, +customized name, and access settings. A completed connection remains even if +subsequent agent creation fails or is cancelled. + +## Runtime isolation + +`prepareManagedAiRuntime` is shared by runs, environment tests, and adoption. +It checks responsible identity, membership, compatibility, connection health, +human audience and agent installation before reading credentials. +Missing credentials produce an actionable configuration failure; responsible-user +task runs use the existing connection-request interaction, marked `purpose: ai`. +A runtime-auth request cannot satisfy, reuse, or supersede a tool request for +the same provider. AI-only methods are excluded from agent tool discovery. + +Each invocation receives a private authentication home and only the selected +grant's credentials. Inherited credential variables are cleared. Conflicting +project authentication and provider-routing overrides are rejected. Managed +failure cannot reactivate host or legacy credentials. + +Subscription invocations take a grant-scoped database advisory lease. Two +different users' grants can run concurrently; a second invocation of the same +subscription receives a retryable busy response while it is in use. Refreshes +are merged only into the originating active grant, with reconnect/revocation +version checks. Temporary homes are removed on normal completion or failure. + +Session reuse includes grant identity, responsible user, and credential +generation. A changed identity starts a fresh provider session. Managed native +executions use per-turn lifecycle cleanup; a suspended native execution whose +credential identity changed must restart as a new execution. + +Revocation blocks new invocations and refresh persistence. A running provider +process may already hold credentials. The revoke confirmation lists attributed +active runs and exposes the existing Stop action; it does not promise immediate +provider-side revocation. + +## Legacy adoption + +Migration `0273` indexes only explicitly owned personal secrets with a recognized +provider/method and matching agent configuration. It keeps original secret +references and leaves every agent's legacy authentication unchanged. Reconnecting +an indexed account creates a private grant credential instead of rotating the +legacy secret. Subsequent reconnects rotate that private credential. Unknown +ownership and filesystem-only subscriptions remain unresolved. The migration is +repeatable and does not classify unknown credentials as company-shared. + +Imported accounts initially need validation. Agent settings show “Existing +authentication — not managed by Connections” until adoption. The adoption +confirmation names the binding and affected agent. Saving runs a provider hello +test in that agent's environment before replacing authentication. After adoption, +the server preserves the managed binding and will not restore legacy fallback. + +## Local subscription sign-in + +Local installations do not need a sandbox to connect a subscription. Connections, +onboarding, and agent setup share `LocalProviderLoginInstructions` and +`useLocalAiLogin`. In local-trusted mode, Claude checks the operator’s existing +Claude Code login. Authenticated self-hosted users instead get a separate +`CLAUDE_CONFIG_DIR` for `claude auth login`; checking and saving only read that +attempt’s credential files, never the server operator’s account or Keychain. + +Codex and Grok start a separate terminal sign-in for each connection or reconnect. +The shared component shows a server-generated command with a fresh `CODEX_HOME` +or `GROK_HOME`. Codex uses file credential storage in that home and `login --device-auth`, so +signing in from another computer does not depend on a localhost callback. The home is never +seeded with the operator's existing login: copying a rotating refresh token would +allow managed runs to invalidate credentials still used by legacy agents or the +operator's terminal. The user completes browser sign-in from that command, then +clicks Connect. This does not require a sandbox or change the host login. + +Attempts reuse `adapter_auth_sessions`, binding company, owner, provider, access +intent, reconnect target, and a 30-minute expiry. Validation and completion are +serialized; duplicate completion returns the saved connection. Restart retains +the attempt. Cancellation and expiry remove the attempt home, and the startup/ +periodic cleanup sweep retries expired directories. Successful completion persists +credentials to the encrypted grant and removes the temporary login home. Refreshes +subsequently update only that grant. Reconnect preserves IDs and access settings. + +Starting an isolated attempt requires normal company-scoped AI-connection creation +permission. Checks, completion, cancellation, and resumption are owner-bound. +Authenticated users cannot import host credentials or use another user’s attempt. +Claude Keychain reads remain limited to the explicit local-trusted default-home import. A failed verification creates +no healthy connection. Preview-era Codex/Grok managed connections without the +isolated-subscription marker require reconnect before another managed execution; +unmanaged legacy agents retain their existing authentication paths. + +## Verification + +`server/src/__tests__/ai-connections.test.ts` exercises storage, isolation, +defaults, human audiences, agent access, reconnect races, refresh ownership, +subscription locking, migration replay, and redacted API failures against a real +embedded database. Existing login, adapter, tool, and channel suites cover their +shared integration paths. The onboarding tests cover managed reuse and keeping a +successfully connected account after failed agent creation. + +The [Storybook review index](http://localhost:6116/?path=/story/ai-connections-review--review-index) +retains deterministic authentication states and interaction checks. Run +`pnpm build-storybook`, then +`pnpm exec playwright test --config tests/ai-connections-review/playwright.config.ts`. +Also run token gates, repository typecheck, tests, and build before handoff. +Live connect → reuse → run → reconnect verification still requires valid provider +credentials and a supported login/runtime environment; fixtures do not prove it. + +For an isolated running test drive, also run: + +```sh +AI_CONNECTIONS_TEST_COMPANY_ID= pnpm exec playwright test --config tests/ai-connections-app/playwright.config.ts +``` + +Set `AI_CONNECTIONS_TEST_URL` when the test drive uses a port other than 3100. + +These browser checks exercise the production list/detail pages, rejected API-key +validation, cancellation, focus restoration, and adoption without saving agent +changes. They submit an explicitly invalid fixture key and do not prove successful +authentication with a live account. + +### Local sign-in checks + +Local subscription screens share the same credential check on entry and when the +window regains focus. Waiting screens also poll until sign-in verifies. A successful +check shows the account is signed in; only **Connect** creates or reconnects the grant. +In local-trusted mode, Claude checks the local operator’s Claude Code login. +Authenticated Claude users, plus all Codex and Grok users, check only their +connection-specific login home. The health response selects credential isolation, +not whether a self-hosted user may sign in. + +Leaving and returning to a local sign-in screen resumes its active attempt. Navigation +does not delete a directory referenced by a copied command. **Start sign-in again** +explicitly cancels the old attempt; abandoned attempts expire after 30 minutes. +Commands create their directory if necessary, and completed/expired attempts are +cleaned up through the existing lifecycle. + +### Disposable live inline-repair test + +The normal app test configuration excludes `*.live.spec.ts`. To run the destructive +inline-repair scenario, set `AI_REPAIR_TEST_ALLOW_DESTRUCTIVE=1` and use a separate +loopback `local_trusted` instance. Set `AI_REPAIR_TEST_DISPOSABLE_MARKER` to a fresh +32-character lowercase hexadecimal value. The company, single Codex agent, single +personal OpenAI API connection, and issue must all be named `AI Repair QA ` +(the issue uses that title). Supply their IDs with `AI_CONNECTIONS_TEST_COMPANY_ID`, +`AI_REPAIR_TEST_CONNECTION_ID`, and `AI_REPAIR_TEST_ISSUE_ID`, and the disposable +provider key with `AI_REPAIR_TEST_KEY`. The test verifies these boundaries before +revoking credentials or submitting work. Delete the disposable instance and revoke +its provider key after the test; failed tests may leave a paused task for inspection. + +Authenticated public deployments must configure a trusted runtime host (`PAPERCLIP_TRUSTED_MCP_RUNTIME_HOST` or `PAPERCLIP_TOOL_RUNTIME_TRUSTED_HOST`) before offering server-host subscription login, matching the local stdio runtime boundary. Health reports this capability so setup can offer a supported environment or API key instead of an unusable terminal command. Private authenticated self-hosted instances support isolated local login without that extra setting. Isolated Claude credential files must be private, owned by the server user, bounded, and free of symlinks. diff --git a/doc/connections/CONNECTOR-PLAYBOOK.md b/doc/connections/CONNECTOR-PLAYBOOK.md index 6756b13e63..5124927b72 100644 --- a/doc/connections/CONNECTOR-PLAYBOOK.md +++ b/doc/connections/CONNECTOR-PLAYBOOK.md @@ -35,6 +35,13 @@ Paperclip resolves short-lived tokens at invocation time. Before writing a connector, read [Identity vs. connections](./README.md#identity-vs-connections) for the P1/P2/P3 boundary and the D7 standing rule. +AI provider credentials use the same vault, applications, grants, installations, +and delegation model with `connectionPurpose: ai` and `transport: runtime_auth`. +They authenticate provider execution and never enter MCP discovery or tool/channel +execution. Extend the provider's existing catalog entry with typed AI methods; +reuse the existing login controllers. See [AI Connections](./AI-CONNECTIONS.md) +for compatibility, personal defaults, resolver isolation, and legacy adoption. + ## Contents - [Mental model and support matrix](#mental-model-five-independent-axes) diff --git a/doc/connections/IMESSAGE-PHOTON-VERIFICATION.md b/doc/connections/IMESSAGE-PHOTON-VERIFICATION.md new file mode 100644 index 0000000000..2151eb66ca --- /dev/null +++ b/doc/connections/IMESSAGE-PHOTON-VERIFICATION.md @@ -0,0 +1,286 @@ +# iMessage Photon verification + +Date: 2026-09-11. Branch: `codex/imessage-photon`. +Base inspected: `1c4bcff2b`; updated through master `ab15aff39`. +Initial implementation checked: `7ada38eb7ef5dff5441f23c02131798b11d57712`. +**Status: experimental; Pro shared-DM live journeys verified below. Dedicated groups and the remaining release matrix are not yet qualified.** + +[PR #13299](https://github.com/paperclipai/paperclip/pull/13299) carries the current +CI and review results. The Photon migration is `0275_easy_dragon_man.sql`, regenerated after master added its own 0274 agent-chat migration. Greptile reviewed the implementation commit at 5/5 with no +actionable comments. This record distinguishes local evidence from live proof. + +## Environment and versions + +- Fresh worktree: `imessage-photon`; separate worktree configuration, instance, + database/storage home, and application port 3109. +- Browser tests use a disposable local-trusted instance on port 3319 with a new + database and storage home. They mock the provider/control-plane responses. +- Database integration tests use disposable embedded PostgreSQL with real channel, + identity, task, attachment, publication, and interaction services. +- Advanced SDK 2.1.0; grpc-js 1.14.4; nice-grpc 2.1.17; nice-grpc-common 2.0.4; + heif2jpeg 0.1.6. Local converter execution: macOS arm64. +- The synthetic HEIC fixture is generated from a solid-color 16×16 image. It has + no personal photo content and does not qualify real iPhone HEIC/Live Photos. +- Production credentials, line tokens, phone numbers, and participant identifiers + are absent from this record. Test numbers/IDs in fixtures are synthetic. + +The primary-instance seed attempt encountered existing source schema drift +(`tool_connections_transport_check` missing), so the isolated worktree uses a clean +instance. The primary database was not modified. Several test starts also reached +macOS's 32-segment System V shared-memory limit. Only unattached IPC from this task's +exited browser-test databases was eligible for cleanup; running instances were not +stopped or altered. + +## Deterministic acceptance evidence + +`server/src/__tests__/photon/photon.test.ts` exercises Basic Cloud authentication, +token redaction, dedicated/shared/missing allocation, immutable line identity, +Unicode multipart publication, receipt recovery, unknown sends and explicit retry, +upload receipt reuse, quota classification, per-part authorization, contiguous +checkpoint recovery, ignored event frames, cutoff history, lease loss, real local +gRPC framing/authentication, scoped state, duplicate-title poll IDs, poll creation +before a local crash, answer parsing, source ownership, image bounds, and actual +synthetic HEIC conversion. + +`server/src/__tests__/photon/channel.integration.test.ts` composes the real channel +service with the Photon adapter and synthetic provider responses. It proves the +fresh linked-message/task/agent-publication setup requirement, restored DM reply, +echo filtering, identity reservation, explicit group enablement, authorized poll +resolution, per-person answer drafts, rejection reasons, exactly one canonical +continuation record, delayed HEIC retry after restart, attachment provenance, +quoted context, task generations, stale controls, retained pending input through +pause, group removal, and a native continuation proof for a second group person. +The checkpoint takeover test verifies the database lease and checkpoint update +share one transaction. + +The native continuation test caught a JSON key-order mismatch after JSONB storage. +Both the recorded answer digest and reconstructed proof now use the existing +canonical hash. This is a native authorization composition test, not evidence of +a live model turn through Photon. + +The Photon/OpenAPI follow-up also verifies safe setup credential, quota, network, +and invalid-response errors, the complete board-only inspection contract, group +participant response fields, and the unchanged credential binding after rejected +replacement. All 39 Photon/OpenAPI tests and the server build passed after the +review fix separating provider outages from invalid setup input. + +The Photon browser cases in `tests/e2e/chat-adapters-ui.spec.ts` cover catalog +discovery, multiple-line selection, password input, keyboard selection, vaulted +credential payload shape, setup completion, group enablement, light/dark themes, +mobile navigation/layout, and pause/resume. The surrounding suite covers existing +Slack, Discord, GitHub, Teams, and Telegram surfaces. + +| Check | Result | +| --- | --- | +| Photon targeted tests | 31 passed, including checkpoint takeover, Live Photo companion retention, and native continuation authorization. | +| Token gates | Passed. All four gates clean. | +| Workspace typecheck | Full `pnpm -r typecheck` passed before and after rebase. | +| Full chat-adapters browser suite | 38 passed, including Photon light/dark/mobile coverage and existing providers. | +| OpenAPI contract | 8 passed, including mounted-route completeness, board-only inspection, and token-free response schemas. | +| Post-rebase channel/native checks | 96 passed across Photon, OpenAPI, explicit native continuation, and chat-control admission retry. | +| Native session resume | 37 passed after building the required local fake-provider binary. | +| UI Vitest project | 6,008 passed across 582 files after rebase. | +| Shared catalog project | 727 passed, including exact catalog and branding coverage. | +| Repository Vitest suite | The initial `pnpm test:run` overlapped edits/rebase and was stopped; it is not a final-commit pass. Fresh targeted and CI checks supersede it. The serialized route run found the missing Photon OpenAPI contract, which is fixed and passes its 8-case suite. Full gate status is recorded in the linked PR. | +| Build | Full `pnpm build` passed before and after rebase. | +| Generated forward migration | Generated through `pnpm db:generate`; `@paperclipai/db check:migrations` passed. Disposable database migrations exercised by integration tests. | +| Native HEIF platform packages | macOS arm64 executed; other published platforms not executed. | + +### Local test prerequisites + +The standard `pnpm test:run` launcher isolates `PAPERCLIP_CONFIG`, `PAPERCLIP_HOME`, +and temporary files. Direct heartbeat/continuation tests must use equivalent +isolation; otherwise the worktree preview configuration suppresses execution. +The actual runner-driver fixture also requires: + +```sh +cargo build --manifest-path packages/paperclip-runner/runner/Cargo.toml --bin fake-codex-app-server +``` + +A run without that binary failed at provider startup; the complete 37-case native +session-resume suite passed after building it. Catalog assertions were updated +for the 42nd visible app, and the focused catalog/Browse/board-gallery tests pass. +Some broad package runs encountered host embedded-Postgres startup limits during +concurrent local development. These startup failures are not provider proof; +inspect the linked PR for the current complete gate results. + +## Pro shared-DM live qualification (2026-09-12) + +The operator approved Pro-compatible shared DMs with groups disabled. The live +test uses the isolated instance on port 3109, a Photon Pro project, its enrolled +test participant, and the participant's actual iPhone. The test source was fully +seeded through the worktree CLI; the primary instance remains untouched. + +Observed with SDK 2.1.0 on implementation base `e556f7dbefd3ee738bde7830d69d5e30c4e96872` +plus the shared-DM changes in this PR: + +- Project inspection and vaulted setup succeeded against Photon Cloud's actual + shared allocation. Shared credentials select the fixed shared gateway and a + project-scoped identity, without inventing an owned phone number. +- At 13:16 UTC, the participant sent a fresh iMessage from their iPhone. Photon + delivered it through authenticated recovery. The project-filtered event feed + jumped from an empty cursor to a non-adjacent sequence; the dedicated-only + adjacency check initially stopped in Attention. +- After the shared recovery fix and an isolated server restart, reconnect replayed + the original message at 13:26 UTC. Paperclip discovered the exact sender but + created no conversation/task while the identity was unlinked. The normal private + confirmation flow then linked that identity to the isolated Board account. +- At 13:27–13:28 UTC, the fresh linked request created a task, ran the native + Codex runner, and delivered the requested response back to Apple Messages. +- A native poll created at 13:29 UTC survived restart. Setup initially rejected + interaction answers until the endpoint was active, deadlocking a clarifying + question before final-reply qualification. Photon now permits those responses + during its verified test step with the same identity, generation, and permission + checks. After restart, a fresh vote at 13:33 UTC produced exactly one canonical + answer and one native continuation. Its final reply arrived in Messages. A late + unvote did not undo the answer. Setup then completed normally. +- At 13:35–13:37 UTC, two free-text answers were collected sequentially. Early + submission stayed pending; explicit submission of both drafts resumed the + native agent with both exact values. The test also corrected the missing-answer + hint to identify the unanswered question rather than always question 1. +- At 13:38 UTC, the initial PNG/document import failed visibly because the shared + gateway returns project attachment aliases in metadata and native UUIDs in + stream headers. Shared downloads now retain authenticated source-message/chat + checks and the exact alias-addressed RPC, validate the header metadata, and + retain project aliases for provenance and restart. At 13:43–13:44 UTC, a fresh + two-file message sent during the outage was recovered, imported, inspected by + the agent, and returned as actual PNG and text-file attachments in Messages. + The agent correctly identified the image and the document's verification word. +- At 13:46–13:49 UTC, a canonical `request_confirmation` rejected a bare Reject + reply with an actionable reason request. A correlated rejection with a reason + resolved the canonical interaction and resumed the native agent, which returned + the exact reason and confirmed that no further action ran. +- At 13:49–13:50 UTC, a synthetic HEIC was sent through Apple Messages. Paperclip + retained the 676-byte original and created a 633-byte JPEG derivative. The agent + correctly described the solid blue 16×16 image and returned the original HEIC + through Photon; the file appeared in Messages. This tests the real transport and + converter together, but does not substitute for an actual iPhone camera photo. +- At 13:51–13:53 UTC, Pause suppressed a delivered test message without creating + a task. Resume did not replay it as work; a fresh request created the next task + and received a reply. Reconnect reused the vaulted credentials and preserved + project/allocation identity, then completed its fresh-message/reply test. +- At 13:53–13:54 UTC, revoking the linked identity caused the next live message to + be filtered with no task or agent run. The normal private confirmation flow + restored the link. Completed tasks remained idle between fresh requests, and + `/status` correctly reported no active task. `/new` requested a fresh message, + and `/close` closed the next active conversation. Its late correlated answer + left the old interaction unresolved and did not start another task. +- At 13:56 UTC, Remove connection archived the test endpoint and its connection, + cleared saved secret bindings, and stopped intake. A message sent while removed + created no task. The same Photon project remained eligible in new setup. + A replacement endpoint was linked normally and completed a fresh native + task/reply test at 13:58 UTC. The test channel was left active. +- At 18:13–18:14 UTC, an operator-supplied iPhone camera HEIC passed the same + authorized Apple Messages conversation on code commit `fc4e4f0a3` (documentation + head `a2a9319f3`). Messages transformed the 1,432,391-byte source into a + 1,132,602-byte HEIC before ingestion. Paperclip retained those received bytes + and generated a 783,443-byte, 3024×4032 JPEG preview. The native agent correctly + described the photo, then staged the HEIC with the same SHA-256 as the received + original. Text and file publications each succeeded on their first attempt with + provider receipts, and the returned photo appeared in Apple Messages. The native + run succeeded and the task completed. The personal photo is not included in the + repository or this report. This closes the real camera HEIC round-trip gap; + Live Photo reassembly remains outside scope. +- An identical published test send was repeated with its original key, exact + payload digest, and reply target. Photon suppressed it but returned gRPC 6 with + SDK `internalError` and an empty context, saying the operation was already + processed. No new bubble appeared. Contrary to the documented original-result + behavior, the shared gateway supplied no receipt. A regression test preserves + delivery-unknown state in this case; no text matching or new key is used. +- The shared receiver now commits only after the complete ordered replay barrier. + Regression cases cover sparse events, interrupted/out-of-order replay, and cursor + resets without advancing the saved checkpoint. Dedicated recovery remains strict. +- All 39 chat-adapters browser tests passed, including shared setup after reload + and existing provider coverage. The 20 Photon unit cases passed. An integration + rerun initially hit the host's embedded-Postgres startup limit; this is a test + environment failure, not a provider result. + +The expanded unit suite has 22 passing cases, including shared attachment alias +ownership, header validation, and missing duplicate receipts. After merging master +and regenerating migration 0275, all 16 integration cases passed at code commit +`fc4e4f0a32d35e41e56f6698404fca64cee3f32b`. Full workspace typecheck, build, token +gates, and migration checks passed on that commit. The isolated instance then +restarted successfully, reported startup ready on that commit, and retained the +active shared-DM endpoint and linked identity. A broad `pnpm test:run` was started and stopped +when the host's shared-memory limit prevented the live isolated PostgreSQL from +restarting. Only this task's exited test database resources were removed. This +interrupted run is not a full-suite pass; current CI must qualify the final commit. + +All 30 applicable CI checks passed on `a2a9319f3`, with two skipped checks. One +unchanged Cursor sandbox command-selection case initially exceeded its 10-second +timeout. The exact case passed locally in 735 ms, and the failed CI server shard +passed on its single rerun. Greptile rated that head 5/5 with no unresolved review +threads. The 22 Photon unit cases also passed in Linux CI, including native HEIC +conversion. Subsequent changes to this record add qualification evidence only; +the linked PR shows their current check status. + +Photon's CLI manages projects and users; its terminal provider simulates chat UI. +Neither substitutes for actual Cloud iMessage delivery. The local Mac initially +classified the assigned number as RCS, while the participant's iPhone sent the +observed iMessage. No RCS/SMS fallback was enabled. + +## Live qualification still required + +Dedicated-line credentials were unavailable during the initial implementation. +The Pro shared-DM journeys above passed; the remaining matrix must be completed +before release readiness. Live inbound receipt alone is not full qualification. +Record the tested commit, package versions, redacted project/line/chat IDs, +participants, timestamps, and observable results when running it. + +| Live case | Status | +| --- | --- | +| Linked DM creates task and receives actual agent response | Passed with Pro shared DMs and the native Codex runner. | +| Enabled group with two linked people preserves attribution | Disabled for the approved Pro scope; dedicated-line live qualification remains unrun. | +| Unlinked sender cannot start work | Passed for the live shared-DM probe; sender discovered, zero conversations/tasks created. | +| Inbound/outbound photos and real iPhone HEIC | Passed for PNG, text file, synthetic HEIC, and an operator-supplied iPhone camera HEIC. The real photo produced a full-resolution JPEG preview and a byte-identical return of the received HEIC. | +| Native poll and text answer resume correct interaction | Passed, including sequential drafts, incomplete submission, explicit submission, and one poll continuation. | +| Approval rejection reason reaches canonical interaction | Passed, including missing-reason correction and native continuation. | +| Restart preserves DM/group replies and pending questions | Shared DM recovery and pending native poll passed; dedicated groups remain unrun. | +| Pause/resume/reconnect/removal enforce authority | Passed for Pro DMs. Removal archived the endpoint and connection, cleared secret bindings, and stopped intake. | +| Completed turn stays idle until fresh input | Passed. September 12 correction: two successive real follow-ups reopened PHOTON-17, with no new task. | +| Provider ambiguous-send/idempotency behavior | Real repeated key suppressed duplicates but returned no original receipt. Unknown-send recovery remains an operator action; no induced network-timeout test. | +| HEIF conversion on Linux glibc/Windows and deployment packaging | macOS arm64 and Linux CI conversion passed. Windows execution remains unrun. Linux musl has no packaged converter. | + +Keep this channel behind the existing experimental gate. Mocked tests, synthetic +gRPC, and a visible catalog card do not establish these live results. + +### September 12: persistent conversation and live task bubbles + +The operator reported three messages creating PHOTON-15, PHOTON-16, and +PHOTON-17. Task completion had incorrectly been treated as the end of an +iMessage conversation, and channel admission did not emit the comment event +used by an open task page. The fix preserves the latest task until an explicit +`/new` or `/close`, publishes comment activity after its transaction commits, +and labels inbound human bubbles in both task-chat renderers. + +Tested the fix in the isolated `codex/imessage-photon` worktree on September 12, +2026 at 13:56–13:57 America/Chicago, against the operator's existing Pro DM +endpoint (`99bebf95…3884`) and PHOTON-17 (`bd6d8379…ba15`). Left the task page +open and sent two authorized messages through Apple Messages to the same Photon +conversation, waiting for completion between sends. Both appeared without a +page reload and both reopened PHOTON-17. Its bubbles showed “Sent from +iMessage”; the agent returned “PHOTON-17 live follow-up received” and +“PHOTON-17 still one conversation” through Photon. The earlier task records +were preserved as history. No live `/new` was sent to replace the operator's +current conversation; explicit reset, close, stale controls, duplicate delivery, +restart, and dedicated-group continuity are covered by integration fixtures. + +The live server was then restarted on `4d7222110`. A third message asked the +agent to repeat its previous reply. It appeared live on PHOTON-17 with its +iMessage label, and the agent returned the exact previous reply through Photon. +All 304 focused tests passed on that commit, and the existing Teams completion +boundary passed its separate regression test. + +Interactive Storybook coverage lives under **Connections / iMessage Photon**. +It uses the production catalog card, three-step channel wizard, access and +management pages, and task message bubbles with explicitly simulated provider +actions. Thirteen stories cover catalog discovery, agent selection, credentials, +shared setup, multiple dedicated lines, missing allocation, loading, connecting, +outage recovery, reconnect, identity access, and persistent task follow-ups. +All 26 light/dark Playwright cases passed, including the 390px mobile layout. +The credential and mobile screenshots were inspected. Run with: + +```sh +pnpm build-storybook +pnpm exec playwright test --config tests/storybook-visual/imessage-photon.config.ts +``` diff --git a/doc/connections/IMESSAGE-PHOTON.md b/doc/connections/IMESSAGE-PHOTON.md new file mode 100644 index 0000000000..8cab0841da --- /dev/null +++ b/doc/connections/IMESSAGE-PHOTON.md @@ -0,0 +1,233 @@ +# iMessage Photon + +**Status: experimental. Live-provider qualification is pending.** + +This channel connects one agent to Photon Cloud. Pro shared allocation supports +DMs; dedicated lines also support explicitly enabled groups. A linked +Paperclip person can send a DM, exchange files, answer questions, and respond to +ordinary confirmations. Each conversation remains attached to a Paperclip task. +The connection is a channel with `chat_sdk` transport, not an MCP tool connection. + +## Prerequisites and setup + +1. Enable the existing experimental chat-connectors setting. Open **Apps → + iMessage Photon**, or the agent's **Channels** panel. +2. In the [Photon dashboard](https://app.photon.codes/), obtain a project ID + and project secret. Paperclip checks the project's actual allocation. Pro + shared allocation is eligible for DMs only. Enroll each sender in the Photon + project's **Users** page and find their assigned number in **Get started**. + This enrollment does not authorize them in Paperclip. See Photon's + [line model](https://photon.codes/docs/spectrum-ts/providers/imessage/connection-and-routing). +3. Choose one invokable agent. Enter the project ID and secret, inspect the + allocation. Connect shared DMs, or select a dedicated line. A single eligible dedicated line is selected + automatically. A number already reserved by any non-archived endpoint in + this instance cannot be selected, including a paused or revoked endpoint. + Shared projects have the same exclusive reservation by project ID. Their + assigned numbers may differ by sender and are not represented as owned numbers. +4. Send a fresh message to the displayed dedicated number, or to the sender's + assigned number from Photon for shared DMs. Link the discovered Messages + identity through Paperclip's identity confirmation flow. Send another fresh + message from that linked person. Setup completes only after a task is created + and an actual agent response is published successfully. +5. With a dedicated line, to use a group, add the number in Apple Messages and send a message to discover + it. Enable the group in Paperclip's Settings page, then send a fresh request. + Discovery does not enable a group or replay the discovery message as work. + +The server needs outbound HTTPS to `spectrum.photon.codes` and TLS gRPC to the +selected `.imsg.photon.codes:443` endpoint, or +`imessage.spectrum.photon.codes:443` for a shared project. No public webhook, Mac Messages +permissions, Spectrum application runtime, or additional agent loop is needed. + +Project secrets are write-only and vaulted. Inspection is restricted to connection +managers and returns project identity, line IDs, phone numbers, and eligibility; +it does not return credentials or line tokens. Agents never receive the project +secret. The server holds short-lived line tokens in memory, renews before expiry, +and checks that the project, line, and number have not changed. Every operation +uses that selected line. Replacing credentials must preserve the same identity; +connect a different identity with a new endpoint. + +Setup and inspection distinguish credential/allocation errors (HTTP 422), quota +limits (429), temporary provider outages (503), and invalid upstream responses +(502). An outage does not mean valid credentials need replacement. A failed +reconnect leaves the existing credential binding intact. + +## Conversation and access rules + +DMs are enabled by default. Dedicated groups start disabled; shared channels +reject groups at admission, publication, and settings changes. Unlinked people cannot +start work unless an operator explicitly enables that setting. Identity links +use the provider-authenticated sender address and service. A phone number and an +Apple-account email are separate identities; names and group membership do not +grant Paperclip authority. Revoked links and inactive/viewer memberships cannot +answer interactions. Guest work retains the shared channel restrictions. + +Enabling a group makes the agent's responses visible to everyone in that group. +It does not authorize every participant to start work. Every authorized message +in an enabled group can start or continue work without a mention. Group names +and participants are displayed in Settings. If the agent's number leaves the +group, that destination becomes unavailable and publication is blocked. + +DMs and groups are linear conversations. An authorized request starts a task; +follow-ups append to the current generation through the ordered delivery queue. +Completing a task ends the current turn. The next message reopens that same task, +including after a server restart. Incoming messages appear live on the open task +as user bubbles labeled “Sent from iMessage.” `/status` shows the current task, +`/close` closes the conversation, +and `/new` closes the current generation so the next request starts a new task. +Quoted message GUIDs and multipart references are retained as task context. +Quotes do not create separate tasks. A quoted control from an older generation +cannot close a newer task. Outgoing echoes, reactions, read receipts, typing, +and nonhuman system messages do not start agent work. + +Messages, tasks, assets, publications, identities, and state remain company-scoped. +Number and shared-project reservations are deliberately instance-wide. Task assignment, budget +limits, pauses, approvals, and native/legacy execution continue through the +existing Paperclip services. + +## Questions and confirmations + +Ordinary `ask_user_questions` uses native polls for closed single-choice questions +with 2–10 options. Prompts include a text alternative. Correlation uses the returned +poll message GUID and option IDs; duplicate titles and option labels are not lookup +keys. Responses from other devices, added options, missing actors, expired prompts, +and later vote changes cannot undo a completed decision. + +Reply to the exact prompt, or use `/answer [.] `. +Numbered choices, comma-separated multiple choices, custom text, and optional +`skip` answers are supported. Questions appear sequentially. Multiple-question +sets save a separate draft for each person and require `/submit `. +Paperclip's canonical validators check required answers and selection/numerical +rules before resolution. Different people cannot contribute to the same draft. + +Ordinary `request_confirmation` offers explicit Accept/Reject. A required rejection +reason is collected through a correlated text response. Target revision, audience, +current identity, task generation, endpoint status, and permissions are rechecked +at submission. Responses resolve through the canonical interaction service and +its durable continuation delivery. A terminal acknowledgement is published once. +Arbitrary “yes” messages and tapbacks never constitute approval. + +Credential proposals, connection authorization, governed tool actions, and review +kinds that need the full review surface remain in Paperclip. The channel supplies +a task link and instructions. No individual-iMessage web permalinks are fabricated. + +## Photos and files + +Text, JPEG/PNG/WebP/GIF, allowed documents, audio, and video use Paperclip's existing +attachment policy and byte limits. Provider upload allowances do not raise those +limits. Attachments are source-bound to the selected line, chat, message, and +attachment GUID before downloading. The server verifies that ownership again on +recovery, bounds metadata, streamed bytes, time, and decoded image dimensions, +and reports rejected/unavailable files in the task. A not-yet-ready attachment +retries before waking the agent, without creating another comment. + +HEIC/HEIF are included in the default attachment policy; operator overrides still +win. The original remains downloadable and a JPEG derivative supplies browser +preview and image input to the agent. The derivative records its source attachment +and hashes. Conversion runs in a separate process with input/output/pixel limits +and a deadline. `heif2jpeg@0.1.6` publishes macOS, Windows, and Linux glibc packages +for x64/arm64; it does not publish Linux musl binaries. A missing or failed converter +retains the original and reports preview unavailability. Only macOS arm64 has been +executed locally for this change; other platform binaries still require qualification. + +Live Photo stills and policy-allowed companion videos are retained as attachments +on the same message. Native Live Photo reconstruction is not implemented. Outbound +files require the existing task/company/agent/originating-run authorization. The +server uploads actual bytes; it never sends private storage URLs to Photon. + +## Publication and recovery + +Only output classified for external publication is sent. Internal commentary, +reasoning, raw tool output, and credentials stay internal. Final responses use +normal bubbles and the channel refreshes typing while work runs. Text is split at +paragraph boundaries with a 4,000-Unicode-code-point target and preserved order. +Source-message reply references are used when the originating run identifies one. +Every text part, attachment message, poll, and explicitly staged correction has a +stable `clientMessageId` and immutable payload. Upload completion is recorded before +the attachment message is sent. Native edits have a bounded window; ordinary final +responses and acknowledgements are separate messages, never token-by-token edits. + +A timeout after transmission is **delivery unknown**. Inspect the activity record +and known Photon receipts, then use Paperclip's operator resolution/retry controls. +Do not retry by creating another publication or changing its key. Explicit retries +reuse the original key and payload. Similar text is not evidence of delivery. An +ambiguous upload without a recorded receipt also needs operator review. + +One elected receiver holds the endpoint lease. Live streams notify a serial +catch-up reader. The reader advances its checkpoint only after preceding events +are durably admitted or classified, including irrelevant events. It deduplicates +provider sequence and message identity independently and reconstructs chats, +attachments, and poll mappings from persisted state after restart. + +Dedicated recovery requires adjacent sequence numbers. The shared gateway's +project-filtered feed has increasing, non-adjacent sequences. Shared recovery +commits its checkpoint only after the complete replay barrier and every preceding +admission succeed. Interrupted or out-of-order replay retains the previous cursor. +Shared channels do not subscribe to the unsupported group stream. + +The pinned SDK's public catch-up iterator discards sequence-only/unknown-variant +frames. Paperclip's small authenticated gRPC recovery transport retains their +sequence while delegating known event decoding to the SDK. This prevents false +history gaps without silently skipping a frame. A missing/reset cursor or an +actual history gap stops in Attention. Initial historical messages establish a +checkpoint but do not create old tasks automatically. + +Pause stops execution and external publication while retaining already accepted +pending work. Resume establishes a new intake cutoff, so messages deliberately +suppressed during pause do not become work. Outage recovery catches up eligible +missed messages. Disconnect archives the endpoint, stops streams, invalidates +interaction authority, and removes owned secret bindings. It does not delete the +Photon project, number, subscription, or Messages history. Hiding experimental +UI alone does not disconnect existing channels. + +## Troubleshooting + +| State or symptom | Action | +| --- | --- | +| Invalid project credentials | Replace the vaulted secret for the same project/number and reconnect. | +| Shared allocation | Connect shared DMs, enroll the sender in Photon, and use their assigned number. Groups require a dedicated line. | +| No eligible dedicated lines | Review the project's line allocation in Photon, then inspect again. | +| Number already owned | Use its existing endpoint or remove that endpoint before reconnecting the number. Pause retains the reservation. | +| Number changes/disappears | Review the Photon allocation. Restore the original identity or create a new endpoint. | +| No task from a group message | Groups are disabled for shared channels. For a dedicated channel, enable the discovered group, link the sender, and send a fresh request. | +| Setup remains Verifying | Complete the linked fresh-message → task → actual agent reply loop; a credential check is insufficient. | +| Quota/network interruption | Review Activity. Transient errors retry with bounded backoff; quotas are distinct from authentication failures. | +| Attachment preparing | Let the durable delivery retry; do not resend the message to force another task. | +| Preview unavailable | Download the original and verify converter support/policy on this deployment platform. | +| Delivery unknown | Reconcile the exact provider receipt or explicitly retry the same immutable publication. | +| Missing/reset cursor or history gap | Review the affected period before operator recovery. The service does not silently skip it. | +| Old poll no longer works | Open the task's current interaction. Completed/expired polls cannot reverse a decision. | + +Diagnostics use existing local activity and run records. This change adds no +first-party Telemetry events. Persisted receipts/checkpoints are required for +recovery; do not manually delete provider state to resolve an outage. + +## Qualification and source versions + +See [implementation and acceptance plan](../plans/2026-09-11-imessage-photon.md) +and [verification record](IMESSAGE-PHOTON-VERIFICATION.md). Deterministic fixtures +and synthetic gRPC are not live-provider proof. A dedicated test line, known +participants, real iPhone HEIC, and native polls are required before claiming the +full live acceptance loop. + +Pinned dependencies: `@photon-ai/advanced-imessage@2.1.0`, `@grpc/grpc-js@1.14.4`, +`nice-grpc@2.1.17`, `nice-grpc-common@2.0.4`, `heif2jpeg@0.1.6`. + +First-party references inspected on 2026-09-11: +[Cloud authentication](https://github.com/photon-hq/spectrum-ts/blob/main/packages/core/src/utils/cloud.ts), +[SDK](https://github.com/photon-hq/advanced-imessage-ts), +[events](https://photon.codes/docs/advanced-kits/imessage/events), +[polls](https://photon.codes/docs/advanced-kits/imessage/polls), +[attachments](https://photon.codes/docs/advanced-kits/imessage/attachments), +[idempotency](https://photon.codes/docs/advanced-kits/imessage/error-handling), and +[HEIF converter](https://photon.codes/docs/utilities/heif2jpeg). + +### Shared-gateway duplicate receipts + +The Pro shared gateway has been observed returning gRPC `ALREADY_EXISTS` as SDK +`internalError`, without a receipt, when an identical `clientMessageId` is repeated. +Paperclip retains delivery-unknown state if no stored receipt exists. Inspect the +original conversation and use the existing operator resolution action. Do not +create another idempotency key or infer delivery from matching text. Photon’s +[documented idempotency behavior](https://photon.codes/docs/advanced-kits/imessage/error-handling) +says repeated writes return the original result; the live shared-gateway result +is recorded separately in the verification report. diff --git a/doc/connections/README.md b/doc/connections/README.md index 9e71188b58..f604effdba 100644 --- a/doc/connections/README.md +++ b/doc/connections/README.md @@ -8,8 +8,11 @@ agent tutorial from provider research and protocol classification through manifest generation, branding, secrets, deterministic tests, real-account proof, and PR submission. +Runtime authentication: [AI Connections](./AI-CONNECTIONS.md). + Provider notes: [Google Workspace](./GOOGLE-WORKSPACE.md), -[Gmail](./GMAIL.md), [PostHog](./POSTHOG.md). Optional credential custody: +[Gmail](./GMAIL.md), [PostHog](./POSTHOG.md), +[AgentMail](./AGENTMAIL.md), and [iMessage Photon](./IMESSAGE-PHOTON.md). Optional credential custody: [Vercel Connect](./VERCEL-CONNECT.md). Post-read action: classify a new integration request, pick the right Paperclip diff --git a/doc/execution-semantics.md b/doc/execution-semantics.md index 1974a4ec33..e6ac4c32ce 100644 --- a/doc/execution-semantics.md +++ b/doc/execution-semantics.md @@ -1009,3 +1009,67 @@ Admission atomically settles an unclaimed coordinator and admits one fresh turn, preserving history, unknown action outcomes, and attempt counts. Pauses, approvals, budgets, task ownership, and terminal task status still gate admission. No automatic provider replay is authorized by a cancelled startup. + +### Delivering queued messages after a legacy run stops + +The legacy queued-message Interrupt action accepts a null `targetRunId` when +there is no active turn. It validates the queue identity and revision under +the task lock and records durable board intent to send the saved queue. A +run that stops between the queue read and the click is also accepted. The +server never redirects interruption to an unrelated active run. +Intentional interruption does not show the global cancelled/failed run toast; +the queue control supplies its own delivery feedback. + +This click can authorize a fresh conversation for messages written before +the prior run stopped. It preserves the original message content and authors, +and retains process/lease stop proofs, task ownership, pauses, approvals, and +budget checks. Queue edits and discards remain authoritative until dispatch. +Dispatch revalidates the consumed queue receipt against the operator, task, +agent, message, and successor run; the operator need not be the message author. +Repeated delivery attempts cannot create another successor after the queue +is consumed. Native same-turn steering retains its active-target contract. + +Legacy finalization retries deferred input after adapter and lease cleanup. +The scheduler also revisits bounded batches of stranded queues after restart +or a late enqueue. Both use normal admission; an existing queued successor +owns the next turn even before it acquires the task execution lock. A recovery +hold or a plain operator Stop does not by itself authorize old input. The +successor guard is scoped to the same agent so another agent's review +participation keeps its independent recovery path. + +An explicit queued-message Interrupt also grants one scoped cleanup retry for +the stopped run. Old ephemeral leases whose cleanup predates provider stop +receipts are rechecked through the recorded provider teardown path. Retained +resources and sandboxes owned by another lease are not rechecked this way. +Delivery still requires the provider's verified stop receipt. Periodic queue +retries do not gain extra cleanup attempts, and the queue displays the server's +waiting reason while cleanup remains unresolved. + +### Operator identity and permission for manual dispatch + +A legacy queued-message Interrupt is a new instruction from the user who clicks +it. The new run uses that user's execution identity, including when someone else +wrote the queued messages. Message bodies and historical authors stay unchanged. +The task page and pipeline conversations both permit Interrupt after the target +run stops and submit the queue's current revision. +Startup validates the consumed queue receipt against the new run, company, +agent, task, clicking user, and delivered message IDs. Automatic retries inherit +the resulting execution identity through the ordinary run identity history. + +Starting an existing agent requires `agent:wake`, which active non-viewer board +members have within their company. Both wake endpoints use this action instead +of `agents:create`. An exact task retry also checks `issue:comment` on the task +from the stored failed run and verifies that its assigned agent has not changed. +External chat retries retain their additional conversation authorization. +Ordinary board wake requests also persist the clicking user's identity, so +adopting another author's queued message cannot change their execution authority. +If that wake merges into an older deferred request, the same transaction updates +the request's execution requester to the clicking user. +Manual wake requests wait for their own run and execution identity. They do not +merge into an agent's active run, with or without a task. +Private agent conversations retain their owner-only wake and retry checks. + +These actions do not grant permission to hire agents or change their settings. +Each action during execution still checks the agent's authority and the +responsible user's authority. A denied retry returns before dispatch; it does +not create a new failed run or change the task's state. diff --git a/doc/plans/2026-09-10-ai-connections-review.md b/doc/plans/2026-09-10-ai-connections-review.md new file mode 100644 index 0000000000..af0df72fd4 --- /dev/null +++ b/doc/plans/2026-09-10-ai-connections-review.md @@ -0,0 +1,81 @@ +# AI Connections — Storybook review milestone + +Status: UI review approved. The app integration is implemented; see [AI Connections](../connections/AI-CONNECTIONS.md) for contracts, runtime selection, adoption, and verification. + +## Start here + +- [Existing Connectors page with AI accounts](http://localhost:6116/?path=/story/ai-connections-review--provider-catalog) +- [Account details in the existing page](http://localhost:6116/?path=/story/ai-connections-review--management) +- [Add account through the existing setup flow](http://localhost:6116/?path=/story/ai-connections-review--connect-from-existing-catalog) +- [Existing inline task connection host](http://localhost:6116/?path=/story/ai-connections-review--inline-task-connection) +- [Review index](http://localhost:6116/?path=/story/ai-connections-review--review-index) + +The old `provider-catalog` URL is retained so links still work. It now renders the real `Browse` page, with AI accounts alongside GitHub and Gmail. It is not another product screen or another provider catalog. + +Build and serve with `pnpm build-storybook` and `node scripts/serve-storybook-static.mjs --port 6116`. Alternatively run `pnpm storybook` on its normal development port. + +The agent picker omits the personal-account inventory and its default/authorization actions. It retains the responsible-user default preview and compatible shared or already-authorized selections. **Change Personal Default** now exercises the existing account detail page; **Authorize Personal** isolates the owner-consent dialog. + +Account details are deliberately compact: a personal-default row with a colored star/check when active, plus credential identity and reconnect/revoke actions. Agent usage and the redundant back button are removed. The preview includes the existing BreadcrumbBar for navigation. Revocation details remain in the confirmation dialog. + +## Reading the story frames + +Every AI review story has a **Storybook only · Review guide** above the preview. It identifies the intended app location, existing app components, proposed components, and simulated wrapper/state. Dashed boundaries mark review annotations, not product UI. + +Agent preview headings, harness/model values, form buttons, and provider simulation controls are labeled **Storybook only**. The picker/authentication composition has its own marked component boundary. Real Connectors pages identify the new AI-only section inside the existing page; task stories mark the existing request component. These annotations live exclusively under `ui/storybook/` and do not appear in production. + +## Existing components investigated and reused + +The current `/:company/apps` route renders **Browse**, not the older Connections page. Its actual provider groups, account rows, search, Add account buttons, status icons, owner identities, and management menus are mounted in the stories. A small optional account-detail slot adds AI sign-in method, personal/shared identity, default, and delegation metadata to its existing rows. + +| Existing component | Reuse in this milestone | +| --- | --- | +| `pages/apps/Browse.tsx` | Real Connectors list; existing provider groups and account rows. No standalone AI list. | +| `pages/apps/AppDetail.tsx` | Existing header, naming, identities, permission loading and account status. | +| `app-detail/IdentitiesSection.tsx` | Existing personal/company ownership display, member audience selection, and revoke confirmation dialog. | +| `app-detail/PermissionsPanel.tsx` | Existing agent access radio cards and agent selector. Only the irrelevant tool-action section is replaced with AI account/default controls. | +| `app-detail/AdvancedPanel.tsx` | Existing reconnect banner with an optional provider-auth callback, behind its existing permission check. | +| `features/connections/ConnectionSetupFlow.tsx` | Existing branded setup shell, human/agent access step, navigation, cancellation and reuse flow. Provider login is composed in a credential-content slot. | +| `features/connections/ConnectionIntentInteractionBody.tsx` | Real task card, modal, existing-account choice, completion and return-focus lifecycle. | +| `ConnectionChoiceList` | Extracted from the existing setup flow's account-reuse rows. Both that flow and the AI agent picker render this component. | +| `pages/apps/AppLogo.tsx` | Existing branding component in AI identity summaries; handles local and dark assets. | +| `AdapterLoginChrome`, `AgentConfigForm`, `AgentProviderConnection` | Existing subscription card/code/input presentation extracted into shared wrappers; live lifecycle hooks remain owned by the existing hosts. | +| `OnboardingWizard`, `ModelSourceTiles`, `CredentialModeLink` | Existing onboarding provider/method controls and API credential card remain shared. | + +The separate `AiProviderPicker`, `AiConnectionRow`, and standalone AI management form have been removed. New AI-specific presentation is limited to agent binding selection, personal defaults/delegation, AI account controls, and controlled auth states. The design guide explains these boundaries and shows the shared picker and credential presentation. + +## Fixture boundaries + +`AiConnectorPages` mounts real route components against an isolated in-memory API. `AiTaskConnectionReview` mounts the real connection-request host. They use the production provider catalog, including the existing `anthropic` entry, and the `ai` / `runtime_auth` discriminator. Accounts and provider responses remain fixtures. + +`AiConnectionsReview` covers proposed agent binding/default behavior with deterministic configuration data. Its agent/onboarding hosts are composition previews, not production route integration. The new-agent and onboarding login presentation uses the extracted existing authentication components. + +No fixture contains credential material. Input values are cleared after submission; provider completion is simulated. No story signs in to a provider or grants real access. Personal-default resolution and delegation checks in `model.ts` are presentation validation, not server authorization. + +## Review and verification + +Review the Connectors list first, then open an account, add one, reconnect, reuse it in a task, and exercise the AI binding picker. Theme and viewport controls cover desktop/narrow and light/dark. Keyboard coverage includes the shared chooser buttons, dialogs, and return focus after cancellation/completion. Harness/model values are asserted unchanged by connection selection. + +```sh +pnpm --filter @paperclipai/ui typecheck +pnpm check:token-gates +pnpm --filter @paperclipai/ui exec vitest run src/pages/apps/Browse.test.tsx src/pages/apps/AppDetail.test.tsx src/pages/apps/AppsConnect.test.tsx src/features/connections/ConnectionIntentInteractionBody.test.tsx src/components/ai-connections src/components/AdapterLoginChrome.test.tsx src/components/OnboardingWizard.adapters.test.tsx src/components/OnboardingWizard.test.tsx +pnpm build-storybook +pnpm exec playwright test --config tests/ai-connections-review/playwright.config.ts +``` + +Verified: 277 focused Vitest checks, 53 browser checks across 48 stories, UI typecheck, token gates, and Storybook build. Desktop and narrow screenshots were inspected in light/dark themes. + +Browser checks load every AI review story, await its interaction assertions, reject rendering/play errors, verify review-index links, test keyboard selection, and capture light/dark layouts at desktop and narrow widths while checking overflow. Screenshots remain ignored local test artifacts. + +The counts above record the original UI review. Integration verification is recorded in the implementation handoff; live provider verification requires valid accounts and a supported sign-in environment. + +## Agreed implementation after review + +- Extend existing applications/connections/grants/installations/delegations with an AI purpose and runtime-auth transport. Reuse encrypted secret storage and company access checks; keep tool/channel execution separate. +- Add typed agent bindings and personal defaults keyed by company, user, provider, and sign-in method. First successful personal connection becomes default only when none exists. Additional defaults require explicit selection; revocation never chooses a replacement. +- Resolve personal defaults from the run's responsible user. Shared and dedicated personal bindings are explicit. Only the owner can authorize their personal account across responsible users. +- Resolve the chosen grant before local, sandbox, native-runner, and test execution. Prevent inherited credentials or cached homes from overriding it; partition session/auth reuse by grant identity, preserve refresh ownership, and report actionable missing-access blockers. +- Keep provider, method, harness, and model routing fixed during connection selection. Start with Claude, OpenAI/ChatGPT, OpenRouter, and Grok/xAI, using only supported existing authentication methods and harness integrations. +- Preserve legacy execution until adoption. Index only credentials with reliable ownership; never infer ownership from account-home paths. Test and explicitly save the replacement binding; do not restore legacy fallback after adoption. +- Schema/API, runtime integration, adoption, and production UI wiring are implemented after approval. There is no separate AI Connections feature flag. diff --git a/doc/plans/2026-09-11-imessage-photon.md b/doc/plans/2026-09-11-imessage-photon.md new file mode 100644 index 0000000000..d3d360f6e4 --- /dev/null +++ b/doc/plans/2026-09-11-imessage-photon.md @@ -0,0 +1,239 @@ +# iMessage Photon channel + +Date: 2026-09-11. Status: approved for implementation; qualification tracked below. +Base: origin/master, 1c4bcff2b. Branch: codex/imessage-photon. + +## Approved scope update — 2026-09-12 + +The operator approved Pro-compatible shared DMs with groups disabled. This +supersedes the dedicated-only exclusions below for DMs. Shared setup uses the +project token and fixed `imessage.spectrum.photon.codes:443` gateway, reserves the +project across non-archived endpoints, and derives a project-scoped conversation +and checkpoint namespace. It never claims ownership of a pool phone number. +Sender enrollment in Photon and identity linking in Paperclip are separate gates. +Dedicated lines retain the original behavior. Allocation changes require a new +channel. Native groups remain unavailable on shared channels at every boundary. + +The real qualification uses an isolated clean database, a test-only agent, the +operator's enrolled Messages identity, and the existing Apps wizard. Photon offers +a terminal development provider and control-plane CLI; neither substitutes for +an iMessage Cloud round trip. Record actual live results separately from fixtures. + +## Outcome and defaults + +Add **iMessage Photon** (`imessage-photon`) behind the existing experimental +chat-connectors UI gate, in Apps and each agent's Channels panel. Use Photon +Cloud and one dedicated number per channel/agent. People initiate DMs and +explicitly enabled groups; every authorized group message can start or continue +work without a mention. Require linked Paperclip people by default. Unlinked +senders require an explicit operator opt-in. Never infer authority from a phone +number, email address, display name, or group membership, or merge those identities. + +Messages remain bound to tasks through the existing chat subsystem: company +scope, vaulted credentials, durable admission, endpoint/conversation leases, +ordered wakeups, external principals, task generations, publication outbox, +attachment provenance, budgets, pauses, native and legacy execution. Keep +`connectionPurpose: channel` and `chat_sdk` transport. Chat-approved external +responses publish automatically; AgentMail's explicit email-send policy stays +specific to email. Do not add a second agent loop, Spectrum application runtime, +generic Photon MCP connection, or arbitrary send API. + +Excluded initially: local Mac access, shared-pool numbers, SMS/RCS, unsolicited +new conversations, agent-created groups, calls, location, stickers, backgrounds, +custom iMessage apps, and native Live Photo reassembly. Hiding experimental UI +must not stop existing channels; Pause/Disconnect control runtime behavior. + +## Provider and credentials + +Implement an in-repo Chat SDK adapter using `@photon-ai/advanced-imessage@2.1.0` +with explicit gRPC dependencies. Reference upstream Photon adapter behavior but +persist correlation by IDs, not in-memory poll titles. Separate Cloud client, +receiver/recovery, adapter, attachment, and interaction helpers. + +Accept project ID and write-only project secret. Inspect Photon using Basic +project authentication and its project/token endpoints. Return only project +identity, allocation eligibility, line IDs, and numbers to managers through +`POST /api/chat-endpoints/:endpointId/photon/inspect`; never return minted tokens. +Verify dedicated allocation from actual token data. Auto-select a sole eligible +number; require explicit selection for multiple numbers. Reject shared allocation. +Vault the secret; store validated project/selected-line config separately. Mint +line tokens in memory, renew before expiry, bind every RPC and stream to the +selected instance, and recheck number/ownership on renewal. Missing, changed, +or ineligible line enters Attention. Rotation preserves project and number; +a new identity needs a new endpoint. Retire old ownership before replacement. + +Extend provider contracts across db/shared/server/UI and generate a forward +migration. Reserve the Photon number across companies for every non-archived +endpoint, including paused/revoked endpoints. Persist checkpoints, typed poll +bindings, per-person drafts, immutable send identities, upload receipts, and +side effects in company/endpoint-scoped state/action stores. + +## Setup and management + +Three steps: choose an invokable agent; connect and inspect Photon credentials +and select a dedicated line; test from Apple Messages. Link Photon dashboard and +dedicated-line documentation. Defaults: DMs on, unlinked people off, individual +groups disabled. Show actionable missing/shared/duplicate/invalid credential +errors. Show the copyable number and discovered sender, support the existing +identity-link confirmation. Only a linked sender's fresh message that creates a +task and receives a successful publication completes setup. Credential verification +alone does not. Optional group test: add number in Messages, discover group, +enable it in Paperclip, send a fresh authorized request. + +Management shows agent/project/number, health, receive/send timestamps, discovered +groups and participants/availability/enablement, linked people/revocation, +delivery retry/unknown outcomes, pause/resume/reconnect/disconnect. Explain that +group replies are visible to all members while sender authorization is separate. +Use existing design tokens/components, official branding with provenance, themes, +keyboard/loading/error/narrow-screen states, task links and copyable number; +never invent individual iMessage web permalinks. + +## Conversations and authorization + +DMs/groups are linear: one active task generation per endpoint/chat. Fresh +authorized input creates work when none is active; follow-ups append and use +existing ordered queue/coalescing. Per the September 12 product correction, +terminal tasks reopen on the next message in the same conversation. Only an +explicit `/new` or `/close` followed by a fresh message creates a new generation. +Inbound comments appear live with “Sent from iMessage” attribution. Support `/status`. +Preserve native reply GUID/part as context; it does not create a separate task. +Chronology guards protect later generations from stale controls. Rename/avatar +changes affect presentation only. Bot removal marks a group unavailable and +blocks sends. Ignore outgoing echoes and system/read/typing/reaction/metadata +events as work triggers. Linked identity/current membership and resource/endpoint +policy are rechecked at admission and interaction resolution. + +## Questions and approvals + +Support ordinary ask_user_questions and eligible request_confirmation. Single +select 2–10 canonical options uses native polls with persistent returned poll +GUID/option-ID bindings, plus text fallback. Never match by title/label. Questions +appear sequentially with a short reference. Accept exact native prompt replies +or `/answer `; support free text, custom/numbered multi-select, +optional skipping, and canonical validation including numerical constraints. +Per-person drafts cannot mix; multiple-question sets require `/submit `. +A single question resolves on first valid authorized submission. + +Confirmations show the approved external summary and Accept/Reject, collecting a +required rejection reason via correlated text. Revalidate target revision, +audience, linked user permission, generation and endpoint before using canonical +interaction/continuation services. Arbitrary yes/tapbacks are never approvals. +Credential disclosure, connection authorization, governed tool execution, and +review kinds requiring the full Board surface get an explanation/task link. + +Handle recognized responses before normal comment ingestion. Duplicate votes, +missing actors, stale/expired links, changed membership, late poll changes and +participant-added options cannot resolve. Unvotes only clear unsubmitted drafts. +Exactly one canonical resolution and continuation; terminal acknowledgement; +old polls inert. Test provider poll creation before local binding crash and votes +before binding finalization without title matching or duplicate resolution. + +## Photos, attachments, and publication + +Support text and policy-allowed images/documents/audio/video. Persist a closed +line/chat/message/attachment/optional-part locator before fetching. Authenticate +over selected line and verify attachment ownership via message/chat. Bound +metadata, bytes, time and decoded dimensions. Keep Paperclip's configured limits, +not Photon's larger allowance. Retry attachmentNotReady before agent wake, +without duplicate comments. Preserve attachment-only input, captions, multiple +images and multipart order; surface unavailable/rejected files in the task. + +Add HEIC/HEIF to default policy while honoring overrides. Preserve originals and +produce a labeled JPEG derivative for preview/agent image input through bounded +`heif2jpeg@0.1.6`; verify packaged platform binaries. Preserve provenance. Keep +Live Photo still/allowed companion video as related files. + +Outbound files need existing company/task/agent/originating-run authorization. +Send bytes, never private storage URLs. Immutable outbox carries stable +clientMessageId for each text part/file/poll/correction; retry same key/payload. +Persist upload receipt before send. Split plain readable text at paragraph +boundaries near 4,000 Unicode-safe characters, preserving order/URLs/code. +Final messages plus typing, not token bubbles. Use native reply references. +Edits within provider limits; expired edits fail visibly or require staged correction. +Internal reasoning/commentary/tools/credentials remain internal. + +Transmission timeout is delivery_unknown. Reconcile exact receipts, never +similar text; operator resolution handles unresolved results, never silently +mint a new idempotency key. Do not assume undocumented provider key retention. + +## Receiving and recovery + +Elect one receiver per endpoint with lease renewal/generation fencing. Subscribe +to live message/chat/group/poll events concurrently with catch-up. Dedupe by line +instance/event sequence and message GUID. Advance checkpoint only after all +preceding events are durably admitted/classified, including irrelevant events. +Bound intake; execution follows durable admission. Rebuild chats, attachments, +and poll mappings after restart without SDK caches. + +Initial activation cutoff suppresses historical work while allowing metadata and +checkpoint establishment. Pause stops execution/publication and retains accepted +work; record pause boundaries to suppress intentional pause interval input. +Outages catch up eligible missed events. Missing/reset/gapped history enters +Attention, never silently skips. Distinguish auth/line/quota/network/preparation/ +ambiguous send failures, bound retries, expose recovery actions. Disconnect stops +streams, invalidates interaction authority, archives endpoint/removes owned secret +bindings; never deletes Photon project/number/subscription/history. No webhook. +Diagnostics stay local; any Telemetry change needs separate strict review. + +## Delivery and verification + +1. Worktree/isolation, source versions, shared contracts/state, generated migration. +2. Cloud inspection, line auth/renewal, adapter lifecycle, synthetic gRPC fixtures. +3. Durable ingress, authorization/linking/resources/generations/queue/publication. +4. Attachments/recovery/HEIC, polls/text drafts/confirmations/continuation. +5. Catalog/setup/management, browser checks, docs, live qualification, PR preparation. + +Keep logical commits; existing Slack/Discord/AgentMail and native/legacy paths +remain functional. Acceptance covers invalid/missing/shared/multiple/duplicate +lines; interrupted setup; unlinked/revoked/viewer/wrong-company/wrong-line inputs; +disabled/removed groups; concurrent/ordered bursts and generation commands; +restart/duplicate/out-of-order/takeover/renewal/catch-up/pause; stable multipart +keys, Unicode, rate limits, successful-send crash/unknown/partial uploads/edit +expiry; image-only/multiple/HEIC/corrupt/oversize/delayed files; exact poll IDs, +free/custom/multiselect/partial/submitted/invalid drafts; accept/reject/reason, +stale target, Board race and one continuation; accessible responsive UI. + +Run targeted suites, migration/package checks, affected chat-adapters browser +suite, then `pnpm check:token-gates`, `pnpm -r typecheck`, `pnpm test:run`, +`pnpm build`. Leave lockfile to the repository bot. Update channel setup, +troubleshooting/feature boundaries and relevant spec addendum; regenerate catalog. +Prepare every PR-template section and explicitly report unavailable credentials, +failed checks and unqualified behavior. + +Live proof requires a dedicated test line and known participants: linked DM and +agent reply; enabled group with two linked participants/attribution; unlinked +denial; both-direction photos including real iPhone HEIC; poll/text continuation; +rejection reason; restart retaining DMs/groups/questions; pause/resume/reconnect/ +remove; terminal conversations idle until fresh input. Record tested commit, +versions, redacted IDs, observable outcomes and limitations. Mocks are not live +qualification. Full DM/group/media/interaction/restart/auth loop is required. + +## Primary sources (verified during planning 2026-09-11) + +- https://photon.codes/docs/spectrum-ts/providers/imessage/connection-and-routing +- https://github.com/photon-hq/advanced-imessage-ts +- https://github.com/photon-hq/spectrum-ts/blob/main/packages/core/src/utils/cloud.ts +- https://photon.codes/docs/advanced-kits/imessage/polls +- https://photon.codes/docs/advanced-kits/imessage/attachments +- https://photon.codes/docs/advanced-kits/imessage/events +- https://photon.codes/docs/advanced-kits/imessage/error-handling +- https://photon.codes/docs/utilities/heif2jpeg + +Inspected versions: advanced-imessage 2.1.0, Photon chat adapter 3.2.0, +Spectrum core/iMessage 12.8.0, heif2jpeg 0.1.6. Cloud Basic auth project endpoint: +`https://spectrum.photon.codes/projects/{projectId}/`; token exchange: +`POST .../imessage/tokens`; dedicated response has `auth` and `numbers` maps keyed +by instance ID and `expiresIn`. Line gRPC host: `{instanceId}.imsg.photon.codes:443`. + +## Implementation evidence + +Implemented in the fresh `codex/imessage-photon` worktree. Provider contracts, +forward migration, Cloud inspection, leased recovery, source-bound media, +immutable publication, native interaction continuation, and the setup/management +UI are present. The channel remains experimental. + +The [channel runbook](../connections/IMESSAGE-PHOTON.md) documents operation and +supported boundaries. The [verification record](../connections/IMESSAGE-PHOTON-VERIFICATION.md) +records automated results and the still-unrun live qualification matrix. No live +Photon credentials or approved test participants were available. Implementation +must not be represented as live-provider qualification. diff --git a/package.json b/package.json index ada96cc757..fe7ac6a095 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "smoke:posthog-live": "node scripts/smoke/posthog-live.mjs", "smoke:pipelines-tutorial": "./scripts/smoke/pipelines-tutorial-smoke.sh", "smoke:terminal-bench-loop-skill": "node scripts/smoke/terminal-bench-loop-skill-smoke.mjs", - "test:release-registry": "node --test scripts/verify-release-registry-state.test.mjs scripts/release-package-map.test.mjs scripts/check-release-package-bootstrap.test.mjs scripts/check-no-git-push.test.mjs scripts/release-lib.test.mjs scripts/release-registry-versions.test.mjs scripts/link-plugin-dev-sdk.test.js scripts/acpx-patch-packaging.test.mjs scripts/service-onboard-smoke.test.mjs scripts/docker-onboard-smoke.test.mjs scripts/preview-artifacts.test.mjs", + "test:release-registry": "node --test scripts/verify-release-registry-state.test.mjs scripts/release-package-map.test.mjs scripts/check-release-package-bootstrap.test.mjs scripts/check-no-git-push.test.mjs scripts/release-lib.test.mjs scripts/release-registry-versions.test.mjs scripts/link-plugin-dev-sdk.test.js scripts/acpx-patch-packaging.test.mjs scripts/service-onboard-smoke.test.mjs scripts/docker-onboard-smoke.test.mjs scripts/preview-artifacts.test.mjs scripts/select-cloud-cache.test.mjs", "storybook-visual:baseline": "node scripts/storybook-visual-baseline.mjs", "test:storybook-visual": "node scripts/storybook-visual-baseline.mjs download && node scripts/storybook-visual-baseline.mjs verify && pnpm build-storybook && npx playwright test --config tests/storybook-visual/playwright.config.ts", "test:storybook-visual:update": "node scripts/storybook-visual-baseline.mjs download && pnpm build-storybook && npx playwright test --config tests/storybook-visual/playwright.config.ts --update-snapshots && node scripts/storybook-visual-baseline.mjs pack", @@ -117,7 +117,9 @@ "@agentclientprotocol/claude-agent-acp@0.73.0>@anthropic-ai/claude-agent-sdk": "0.3.263", "rollup": ">=4.59.0", "react": "^19.2.8", - "react-dom": "^19.2.8" + "react-dom": "^19.2.8", + "@codemirror/state": "^6.7.2", + "@codemirror/view": "^6.43.11" } } } diff --git a/packages/adapter-utils/src/acpx-engine/execute.ts b/packages/adapter-utils/src/acpx-engine/execute.ts index 85b64bf161..e61bf94ae2 100644 --- a/packages/adapter-utils/src/acpx-engine/execute.ts +++ b/packages/adapter-utils/src/acpx-engine/execute.ts @@ -2070,7 +2070,7 @@ async function buildRuntime(input: { // device login wrote. This never touches `prepareCodexSkillRuntime` above // — that function stays Codex-only — and every other custom ACPX agent // (for example `kimi`) falls through this branch unaffected. - if (acpxAgent === "grok") { + if (acpxAgent === "grok" && !config.managedAiConnection) { env.GROK_HOME = resolveManagedGrokHomeDir(agent.companyId); } const desired = resolveLegacyPaperclipDesiredSkillNames( diff --git a/packages/adapter-utils/src/chat-file-delivery.ts b/packages/adapter-utils/src/chat-file-delivery.ts index 818fd63bf8..edb3084c38 100644 --- a/packages/adapter-utils/src/chat-file-delivery.ts +++ b/packages/adapter-utils/src/chat-file-delivery.ts @@ -1,6 +1,6 @@ export interface PaperclipChatFilePreparationDelivery { readonly provider: - "slack" | "github" | "discord" | "microsoft-teams" | "telegram" | null; + "slack" | "github" | "discord" | "microsoft-teams" | "telegram" | "imessage-photon" | null; readonly mode: "provider_attachment" | "paperclip_task_only" | "unknown"; readonly preparationState: "prepared"; readonly providerDeliveryConfirmed: false; @@ -35,7 +35,8 @@ export function paperclipChatFilePreparationDelivery( if ( authenticatedProvider === "slack" || authenticatedProvider === "discord" || - authenticatedProvider === "telegram" + authenticatedProvider === "telegram" || + authenticatedProvider === "imessage-photon" ) { return { ...common, diff --git a/packages/adapter-utils/src/server-utils.ts b/packages/adapter-utils/src/server-utils.ts index 465d0969c5..479587bd56 100644 --- a/packages/adapter-utils/src/server-utils.ts +++ b/packages/adapter-utils/src/server-utils.ts @@ -797,7 +797,7 @@ type PaperclipWakeRecovery = { }; export type PaperclipExternalChatProvider = - "slack" | "github" | "discord" | "microsoft-teams" | "telegram"; + "slack" | "github" | "discord" | "microsoft-teams" | "telegram" | "imessage-photon"; type PaperclipWakePayload = { executionContinuation: ExecutionContinuationEnvelope | null; @@ -1665,6 +1665,7 @@ const PAPERCLIP_EXTERNAL_CHAT_PROVIDERS = "discord", "microsoft-teams", "telegram", + "imessage-photon", ]); function normalizePaperclipExternalChatProvider( diff --git a/packages/adapters/claude-local/src/server/acp.ts b/packages/adapters/claude-local/src/server/acp.ts index 951a9419e7..dcd2fbf20a 100644 --- a/packages/adapters/claude-local/src/server/acp.ts +++ b/packages/adapters/claude-local/src/server/acp.ts @@ -227,7 +227,7 @@ async function prepareClaudeRemoteManagedHome( typeof envConfig.CLAUDE_CONFIG_DIR === "string" && envConfig.CLAUDE_CONFIG_DIR.trim().length > 0 ? envConfig.CLAUDE_CONFIG_DIR.trim() : ""; - if (explicitClaudeConfigDir) { + if (explicitClaudeConfigDir && !input.config.managedAiConnection) { // User-managed escape hatch. Unlike the Claude CLI lane // (`claude-local/execute.ts`), which runs the process on the same host and can // forward the operator's path verbatim, the remote ACP lane spawns Claude @@ -267,7 +267,9 @@ async function prepareClaudeRemoteManagedHome( // Content-addressed sanitized seed (managed cache under the instance root, not // a temp dir — reused across runs, so no teardown cleanup). - const claudeConfigSeedDir = await prepareClaudeConfigSeed(process.env, onLog, input.companyId); + const claudeConfigSeedDir = input.config.managedAiConnection + ? explicitClaudeConfigDir + : await prepareClaudeConfigSeed(process.env, onLog, input.companyId); // Ship the per-run skill bundle, staged only when the run selected at // least one skill. The bundle directory holds a plain copy of each // selected skill's files (`materializePaperclipSkillCopy` never copies a @@ -625,6 +627,7 @@ export async function probeClaudeAcpSandboxLogin(input: { } const args = ["--print", "-", "--output-format", "stream-json", "--verbose"]; + if (config.managedAiConnection) args.push("--setting-sources", "user"); args.push( ...buildClaudeProbePermissionArgs({ dangerouslySkipPermissions: asBoolean(config.dangerouslySkipPermissions, true), @@ -760,7 +763,7 @@ export async function testClaudeAcpEnvironment( }); const envConfig = parseObject(config.env); - const considerHostEnv = !targetIsRemote; + const considerHostEnv = !targetIsRemote && !config.managedAiConnection; const hasBedrock = envConfig.CLAUDE_CODE_USE_BEDROCK === "1" || envConfig.CLAUDE_CODE_USE_BEDROCK === "true" || @@ -784,10 +787,10 @@ export async function testClaudeAcpEnvironment( const source = isNonEmpty(configApiKey) ? "adapter config env" : "server environment"; checks.push({ code: "claude_acp_anthropic_api_key_detected", - level: "warn", - message: "ANTHROPIC_API_KEY is set. Claude ACP will use API-key auth instead of subscription credentials.", + level: config.managedAiConnection ? "info" : "warn", + message: config.managedAiConnection ? "Using the selected Claude API connection." : "ANTHROPIC_API_KEY is set. Claude ACP will use API-key auth instead of subscription credentials.", detail: `Detected in ${source}.`, - hint: "Unset ANTHROPIC_API_KEY if you want subscription-based Claude login behavior.", + hint: config.managedAiConnection ? undefined : "Unset ANTHROPIC_API_KEY if you want subscription-based Claude login behavior.", }); } else if ( isNonEmpty(envConfig.CLAUDE_CODE_OAUTH_TOKEN) || @@ -860,6 +863,7 @@ export async function testClaudeAcpEnvironment( const runId = `claude-acp-envtest-${Date.now()}-${Math.random().toString(16).slice(2)}`; checks.push( ...(await prepareSandboxClaudeProbeRuntime({ + managedAiConnection: Boolean(config.managedAiConnection), runId, target, cwd, diff --git a/packages/adapters/claude-local/src/server/claude-config.ts b/packages/adapters/claude-local/src/server/claude-config.ts index 2195a00544..1f9710ca2c 100644 --- a/packages/adapters/claude-local/src/server/claude-config.ts +++ b/packages/adapters/claude-local/src/server/claude-config.ts @@ -269,6 +269,7 @@ function isNonEmptyString(value: unknown): value is string { * the function keeps it and skips the managed materialization. */ export async function prepareSandboxClaudeProbeRuntime(input: { + managedAiConnection?: boolean; runId: string; target: AdapterExecutionTarget | null; cwd: string; @@ -295,12 +296,12 @@ export async function prepareSandboxClaudeProbeRuntime(input: { if ( input.targetIsRemote && adapterExecutionTargetUsesManagedHome(input.target) && - !hasExplicitClaudeConfigDir + (!hasExplicitClaudeConfigDir || input.managedAiConnection) ) { let tempWorkspaceDir: string | null = null; let preparedRuntime: Awaited> | null = null; try { - const seedDir = await prepareClaudeConfigSeed(process.env, async () => {}, input.companyId); + const seedDir = input.managedAiConnection ? input.env.CLAUDE_CONFIG_DIR : await prepareClaudeConfigSeed(process.env, async () => {}, input.companyId); const managedRemoteCwd = input.target?.kind === "remote" ? input.target.remoteCwd : input.cwd; tempWorkspaceDir = await fs.mkdtemp( diff --git a/packages/adapters/claude-local/src/server/execute.ts b/packages/adapters/claude-local/src/server/execute.ts index 6ca44cacb0..dc55a1676c 100644 --- a/packages/adapters/claude-local/src/server/execute.ts +++ b/packages/adapters/claude-local/src/server/execute.ts @@ -567,7 +567,7 @@ export async function execute(ctx: AdapterExecutionContext): Promise { @@ -884,6 +884,7 @@ export async function execute(ctx: AdapterExecutionContext): Promise { const args = ["--print", "--output-format", "stream-json", "--verbose"]; + if (config.managedAiConnection) args.push("--setting-sources", "user"); if (resumeSessionId) args.push("--resume", resumeSessionId); args.push(...buildClaudeExecutionPermissionArgs({ dangerouslySkipPermissions, diff --git a/packages/adapters/claude-local/src/server/quota-keychain.test.ts b/packages/adapters/claude-local/src/server/quota-keychain.test.ts new file mode 100644 index 0000000000..9cc07437d0 --- /dev/null +++ b/packages/adapters/claude-local/src/server/quota-keychain.test.ts @@ -0,0 +1,35 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import { readClaudeToken } from "./quota.js"; +const mocks = vi.hoisted(() => ({ read: vi.fn(), exec: vi.fn() })); +vi.mock("node:fs/promises", () => ({ default: { readFile: mocks.read } })); +vi.mock("node:child_process", () => ({ execFile: Object.assign(vi.fn(), { [Symbol.for("nodejs.util.promisify.custom")]: mocks.exec }) })); +afterEach(() => { vi.resetAllMocks(); vi.unstubAllEnvs(); vi.restoreAllMocks(); }); +describe("explicit Claude Keychain import", () => { + it("does not consult Keychain during passive reads", async () => { + mocks.read.mockRejectedValue(new Error("missing")); + await expect(readClaudeToken()).resolves.toBeNull(); + expect(mocks.exec).not.toHaveBeenCalled(); + }); + it("reads the macOS login only after explicit opt-in", async () => { + vi.spyOn(process, "platform", "get").mockReturnValue("darwin"); + vi.stubEnv("CLAUDE_CONFIG_DIR", ""); + mocks.read.mockRejectedValue(new Error("missing")); + mocks.exec.mockResolvedValue({ stdout: JSON.stringify({ claudeAiOauth: { accessToken: "fixture" } }) }); + await expect(readClaudeToken({ allowKeychain: true })).resolves.toBe("fixture"); + expect(mocks.exec).toHaveBeenCalledWith("/usr/bin/security", ["find-generic-password", "-s", "Claude Code-credentials", "-w"], expect.any(Object)); + }); + it("never substitutes Keychain credentials for a custom auth home", async () => { + vi.spyOn(process, "platform", "get").mockReturnValue("darwin"); + vi.stubEnv("CLAUDE_CONFIG_DIR", "/isolated/auth"); + mocks.read.mockRejectedValue(new Error("missing")); + await expect(readClaudeToken({ allowKeychain: true })).resolves.toBeNull(); + expect(mocks.exec).not.toHaveBeenCalled(); + }); + it("does not surface a credential-bearing subprocess error", async () => { + vi.spyOn(process, "platform", "get").mockReturnValue("darwin"); + vi.stubEnv("CLAUDE_CONFIG_DIR", ""); + mocks.read.mockRejectedValue(new Error("missing")); + mocks.exec.mockRejectedValue(new Error("fixture-secret")); + await expect(readClaudeToken({ allowKeychain: true })).resolves.toBeNull(); + }); +}); diff --git a/packages/adapters/claude-local/src/server/quota.ts b/packages/adapters/claude-local/src/server/quota.ts index eac10e44eb..9b5a264af8 100644 --- a/packages/adapters/claude-local/src/server/quota.ts +++ b/packages/adapters/claude-local/src/server/quota.ts @@ -92,6 +92,10 @@ async function readClaudeTokenFromFile(credPath: string): Promise } catch { return null; } + return parseClaudeCredentialToken(raw); +} + +function parseClaudeCredentialToken(raw: string): string | null { let parsed: unknown; try { parsed = JSON.parse(raw); @@ -137,12 +141,20 @@ function describeClaudeSubscriptionAuth(status: ClaudeAuthStatus | null): string : "Claude is logged in via claude.ai"; } -export async function readClaudeToken(): Promise { +export async function readClaudeToken(options: { allowKeychain?: boolean } = {}): Promise { const configDir = claudeConfigDir(); for (const filename of [".credentials.json", "credentials.json"]) { const token = await readClaudeTokenFromFile(path.join(configDir, filename)); if (token) return token; } + // Only an explicit local-account import may consult the user's Keychain. + // A custom auth home must never fall through to a different account. + if (options.allowKeychain && process.platform === "darwin" && !process.env.CLAUDE_CONFIG_DIR?.trim()) { + try { + const { stdout } = await execFileAsync("/usr/bin/security", ["find-generic-password", "-s", "Claude Code-credentials", "-w"], { timeout: 10000, maxBuffer: 1024 * 1024 }); + return parseClaudeCredentialToken(stdout); + } catch { return null; } + } return null; } diff --git a/packages/adapters/claude-local/src/server/test.probe.test.ts b/packages/adapters/claude-local/src/server/test.probe.test.ts index 4040ba27f5..b8bfe60d14 100644 --- a/packages/adapters/claude-local/src/server/test.probe.test.ts +++ b/packages/adapters/claude-local/src/server/test.probe.test.ts @@ -512,6 +512,16 @@ describe("claude auth mode hints", () => { ).toBe(false); }); + it("reports an intentionally selected managed API account without a subscription warning", async () => { + probeResult.value = { exitCode: 0, stdout: successStdout, stderr: "" }; + const result = await testEnvironment({ companyId: "company-1", adapterType: "claude_local", + config: { engine: "cli", command: "claude", managedAiConnection: { provider: "anthropic", method: "api_key" }, env: { ANTHROPIC_API_KEY: "api-test-key" } }, + executionTarget: sandboxTarget, environmentName: "Daytona", + }); + expect(result.checks.find(check => check.code === "claude_anthropic_api_key_overrides_subscription")).toMatchObject({ level: "info", message: "Using the selected Claude API connection." }); + expect(JSON.stringify(result.checks)).not.toContain("Unset ANTHROPIC_API_KEY"); + }); + it("keeps the API-key warning authoritative when both ANTHROPIC_API_KEY and the token are set", async () => { probeResult.value = { exitCode: 0, stdout: successStdout, stderr: "" }; diff --git a/packages/adapters/claude-local/src/server/test.ts b/packages/adapters/claude-local/src/server/test.ts index cf5940d4f0..507e645ef9 100644 --- a/packages/adapters/claude-local/src/server/test.ts +++ b/packages/adapters/claude-local/src/server/test.ts @@ -135,6 +135,7 @@ export async function testEnvironment( : await buildLocalAdapterTestProbeEnv({ callerEnv: env, trustedEnv: process.env }); checks.push( ...(await prepareSandboxClaudeProbeRuntime({ + managedAiConnection: Boolean(config.managedAiConnection), runId, target, cwd, @@ -177,7 +178,7 @@ export async function testEnvironment( // reflect what the agent will actually see at runtime. Only consider env // vars from the adapter config in that case; the probe itself will surface // any auth issues on the remote box. - const considerHostEnv = !targetIsRemote; + const considerHostEnv = !targetIsRemote && !config.managedAiConnection; const hasBedrock = env.CLAUDE_CODE_USE_BEDROCK === "1" || env.CLAUDE_CODE_USE_BEDROCK === "true" || @@ -206,11 +207,11 @@ export async function testEnvironment( const source = isNonEmpty(configApiKey) ? "adapter config env" : "server environment"; checks.push({ code: "claude_anthropic_api_key_overrides_subscription", - level: "warn", + level: config.managedAiConnection ? "info" : "warn", message: - "ANTHROPIC_API_KEY is set. Claude will use API-key auth instead of subscription credentials.", + config.managedAiConnection ? "Using the selected Claude API connection." : "ANTHROPIC_API_KEY is set. Claude will use API-key auth instead of subscription credentials.", detail: `Detected in ${source}.`, - hint: "Unset ANTHROPIC_API_KEY if you want subscription-based Claude login behavior.", + hint: config.managedAiConnection ? undefined : "Unset ANTHROPIC_API_KEY if you want subscription-based Claude login behavior.", }); } else if ( isNonEmpty(env.CLAUDE_CODE_OAUTH_TOKEN) || @@ -350,6 +351,7 @@ export async function testEnvironment( } const args = ["--print", "-", "--output-format", "stream-json", "--verbose"]; + if (config.managedAiConnection) args.push("--setting-sources", "user"); args.push(...buildClaudeProbePermissionArgs({ dangerouslySkipPermissions, targetIsRemote, diff --git a/packages/adapters/codex-local/src/server/acp.ts b/packages/adapters/codex-local/src/server/acp.ts index 500d7a72f1..ad13d427c3 100644 --- a/packages/adapters/codex-local/src/server/acp.ts +++ b/packages/adapters/codex-local/src/server/acp.ts @@ -206,7 +206,7 @@ async function prepareCodexRemoteManagedHome( restore: async ({ assetDir, readFile }) => void (await copyBackCodexAuth({ readSandboxAuth: () => readFile(path.posix.join(assetDir, "auth.json")), - hostAuthPath: path.join(resolveSharedCodexHomeDir(process.env), "auth.json"), + hostAuthPath: path.join(input.config.managedAiConnection ? effectiveCodexHome : resolveSharedCodexHomeDir(process.env), "auth.json"), log: (line) => onLog("stdout", `${line}\n`), })), }, diff --git a/packages/adapters/codex-local/src/server/execute.ts b/packages/adapters/codex-local/src/server/execute.ts index dc60fabad7..72ac42e5eb 100644 --- a/packages/adapters/codex-local/src/server/execute.ts +++ b/packages/adapters/codex-local/src/server/execute.ts @@ -675,7 +675,7 @@ export async function execute(ctx: AdapterExecutionContext): Promise void (await copyBackCodexAuth({ readSandboxAuth: () => readFile(path.posix.join(assetDir, "auth.json")), - hostAuthPath: path.join(resolveSharedCodexHomeDir(process.env), "auth.json"), + hostAuthPath: path.join(config.managedAiConnection ? effectiveCodexHome : resolveSharedCodexHomeDir(process.env), "auth.json"), log: (line) => onLog("stdout", `${line}\n`), // Additive cache write (sandbox to host): also cache the // sandbox subscription credential in its per-identity slot, // keyed by the real `account_id`. Company-scoped root; the // helper ensures the slot directory private and containment- // guarded. The off-switch (default on) is read inside. - resolveCacheEntryPath: (accountId) => + resolveCacheEntryPath: config.managedAiConnection ? undefined : (accountId) => ensureCodexAuthCacheEntryDir(process.env, accountId, agent.companyId), env: process.env, })), diff --git a/packages/adapters/codex-local/src/server/index.ts b/packages/adapters/codex-local/src/server/index.ts index ef141e2bf4..bc4c9ef0f8 100644 --- a/packages/adapters/codex-local/src/server/index.ts +++ b/packages/adapters/codex-local/src/server/index.ts @@ -120,3 +120,7 @@ export const sessionCodec: AdapterSessionCodec = { ); }, }; + +export { decideCodexAuthMerge } from "./codex-auth-merge-decision.js"; + +export { copyBackCodexAuth } from "./codex-auth-copyback.js"; diff --git a/packages/adapters/codex-local/src/server/test.ts b/packages/adapters/codex-local/src/server/test.ts index 0ff8c38098..f6a5a496b3 100644 --- a/packages/adapters/codex-local/src/server/test.ts +++ b/packages/adapters/codex-local/src/server/test.ts @@ -83,6 +83,7 @@ async function prepareCodexHelloProbe(input: { args: string[]; env: Record; probeApiKey: string | null; + managedAiConnection?: boolean; }): Promise<{ command: string; args: string[]; @@ -118,7 +119,7 @@ async function prepareCodexHelloProbe(input: { const configuredHomeIsManaged = configuredCodexHome != null && isManagedCodexHomePath(process.env, input.companyId, configuredCodexHome); - if (isCodexAuthCacheEnabled(process.env)) { + if (!input.managedAiConnection && isCodexAuthCacheEnabled(process.env)) { // Identity-anchored cache vend, exactly as execute runs it before the // seeding below. Best-effort: a vend failure never blocks the probe, and // the probe then stages the shared credential as-is. @@ -253,6 +254,9 @@ export async function testEnvironment( code: "adapter_engine_unavailable", level: "error", message: engineSelection.unavailableReason, + hint: ctx.executionTarget?.kind === "remote" + ? "In the agent’s runtime settings, select the CLI engine, or use a sandbox image with the Codex ACP server installed." + : undefined, }], testedAt: new Date().toISOString(), }; @@ -409,6 +413,7 @@ export async function testEnvironment( ? hostOpenAiKey : null; const preparedProbe = await prepareCodexHelloProbe({ + managedAiConnection: Boolean(config.managedAiConnection), runId, companyId: ctx.companyId, target, diff --git a/packages/adapters/grok-local/src/server/execute.ts b/packages/adapters/grok-local/src/server/execute.ts index 4a8d5cc500..a255284843 100644 --- a/packages/adapters/grok-local/src/server/execute.ts +++ b/packages/adapters/grok-local/src/server/execute.ts @@ -313,12 +313,12 @@ export async function execute(ctx: AdapterExecutionContext): Promise, "XAI_API_KEY"); + !hasNonEmptyEnvValue(env, "XAI_API_KEY") && (Boolean(config.managedAiConnection) || !hasNonEmptyEnvValue(process.env as Record, "XAI_API_KEY")); if (isGrokSubscriptionMode) { env.GROK_HOME = hostGrokHome; } diff --git a/packages/adapters/grok-local/src/server/index.ts b/packages/adapters/grok-local/src/server/index.ts index 8655065085..85e1b6861d 100644 --- a/packages/adapters/grok-local/src/server/index.ts +++ b/packages/adapters/grok-local/src/server/index.ts @@ -80,3 +80,7 @@ export { type PromoteGrokDeviceLoginCredentialInput, type PromoteGrokDeviceLoginCredentialOutcome, } from "./adapter-auth-promotion.js"; + +export { decideGrokAuthMerge } from "./grok-auth-merge-decision.js"; + +export { parseGrokAuthPayload, hasUsableGrokAuthValue } from "./grok-home.js"; diff --git a/packages/adapters/grok-local/src/server/test.ts b/packages/adapters/grok-local/src/server/test.ts index 81c9617d5a..27aabc38bb 100644 --- a/packages/adapters/grok-local/src/server/test.ts +++ b/packages/adapters/grok-local/src/server/test.ts @@ -12,11 +12,16 @@ import { } from "@paperclipai/adapter-utils/server-utils"; import { describeAdapterExecutionTarget, + prepareAdapterExecutionTargetRuntime, ensureAdapterExecutionTargetCommandResolvable, ensureAdapterExecutionTargetDirectory, resolveAdapterExecutionTargetCwd, runAdapterExecutionTargetProcess, } from "@paperclipai/adapter-utils/execution-target"; +import { rm } from "node:fs/promises"; +import path from "node:path"; +import { stageGrokHomeForSync } from "./grok-home.js"; +import { copyBackGrokAuth } from "./grok-auth-copyback.js"; import { DEFAULT_GROK_LOCAL_MODEL } from "../index.js"; import { parseGrokJsonl } from "./parse.js"; import { ADAPTER_AUTH_MISSING_CHECK_CODE } from "@paperclipai/shared"; @@ -145,6 +150,24 @@ export async function testEnvironment( } const env = normalizeEnv(config.env); + let stagedHome: string | undefined; + let restore: (() => Promise) | undefined; + try { + if (config.managedAiConnection && targetIsRemote) { + const hostHome = env.GROK_HOME; + stagedHome = await stageGrokHomeForSync(hostHome, { runId }); + const prepared = await prepareAdapterExecutionTargetRuntime({ + runId, target, adapterKey: "grok", workspaceLocalDir: cwd, + assets: [{ key: "home", localDir: stagedHome, followSymlinks: true, + restore: async ({ assetDir, readFile }) => { await copyBackGrokAuth({ + readSandboxAuth: () => readFile(path.posix.join(assetDir, "auth.json")), + hostHomeDir: hostHome, log: () => {}, + }); }, + }], + }); + env.GROK_HOME = prepared.assetDirs.home; + restore = () => prepared.restoreWorkspace(() => {}); + } const runtimeEnv = ensurePathInEnv({ ...process.env, ...env }); try { @@ -343,4 +366,5 @@ export async function testEnvironment( checks, testedAt: new Date().toISOString(), }; + } finally { try { await restore?.(); } finally { if (stagedHome) await rm(stagedHome, { recursive: true, force: true }); } } } diff --git a/packages/adapters/opencode-local/src/server/execute.remote.test.ts b/packages/adapters/opencode-local/src/server/execute.remote.test.ts index d3934ad921..7472e11ffe 100644 --- a/packages/adapters/opencode-local/src/server/execute.remote.test.ts +++ b/packages/adapters/opencode-local/src/server/execute.remote.test.ts @@ -126,7 +126,7 @@ describe("opencode remote execution", () => { } }); - it("prepares the workspace, syncs OpenCode skills, and restores workspace changes for remote SSH execution", async () => { + it.each([false, true])("prepares the workspace, syncs OpenCode skills, and restores workspace changes for remote SSH execution (managed=%s)", async (managed) => { const rootDir = await mkdtemp(path.join(os.tmpdir(), "paperclip-opencode-remote-")); cleanupDirs.push(rootDir); const workspaceDir = path.join(rootDir, "workspace"); @@ -153,6 +153,13 @@ describe("opencode remote execution", () => { config: { command: "opencode", model: "opencode/gpt-5-nano", + ...(managed ? { + managedAiConnection: { provider: "openrouter", method: "api_key" }, + } : {}), + env: { + XDG_CONFIG_HOME: path.join(rootDir, "config"), + ...(managed ? { HOME: "/var/folders/qa-managed", XDG_DATA_HOME: "/var/folders/qa-managed/data" } : {}), + }, }, context: { paperclipWorkspace: { @@ -233,6 +240,17 @@ describe("opencode remote execution", () => { | [string, string, string[], { env: Record; remoteExecution?: { remoteCwd: string } | null }] | undefined; expect(call?.[3].env.PAPERCLIP_WORKSPACE_CWD).toBe(managedRemoteWorkspace); + if (managed) { + const home = `${managedRemoteWorkspace}/.paperclip-runtime/opencode/managed-auth/run-1`; + expect(call?.[3].env.HOME).toBe(home); + expect(call?.[3].env.XDG_DATA_HOME).toBe(`${home}/data`); + expect(modelProbeCall?.[3].env.XDG_DATA_HOME).toBe(`${home}/data`); + expect(runSshCommand).toHaveBeenCalledWith( + expect.anything(), + expect.stringContaining(`${home}/.claude/skills`), + expect.anything(), + ); + } expect(JSON.parse(call?.[3].env.PAPERCLIP_WORKSPACES_JSON ?? "[]")).toEqual([ { workspaceId: "workspace-1", diff --git a/packages/adapters/opencode-local/src/server/execute.ts b/packages/adapters/opencode-local/src/server/execute.ts index a97fe6a8e6..ac810e0549 100644 --- a/packages/adapters/opencode-local/src/server/execute.ts +++ b/packages/adapters/opencode-local/src/server/execute.ts @@ -59,7 +59,7 @@ import { requireOpenCodeModelId, } from "./models.js"; import { removeMaintainerOnlySkillSymlinks } from "@paperclipai/adapter-utils/server-utils"; -import { prepareOpenCodeRuntimeConfig } from "./runtime-config.js"; +import { prepareOpenCodeRuntimeConfig, prepareManagedOpenCodeRemoteHomes } from "./runtime-config.js"; import { SANDBOX_INSTALL_COMMAND } from "../index.js"; import { resolveOpenCodeSkillsHome } from "./skills.js"; @@ -438,9 +438,18 @@ export async function execute(ctx: AdapterExecutionContext): Promise; + config: Record; + runtimeRootDir: string | null | undefined; + runId: string; + configDir?: string; +}): void { + if (!input.config.managedAiConnection) return; + if (!input.runtimeRootDir) throw new Error("Managed OpenCode authentication requires an isolated remote runtime directory."); + const home = path.posix.join(input.runtimeRootDir, "managed-auth", input.runId); + Object.assign(input.env, { + HOME: home, + XDG_CONFIG_HOME: input.configDir ?? path.posix.join(home, "config"), + XDG_DATA_HOME: path.posix.join(home, "data"), + XDG_CACHE_HOME: path.posix.join(home, "cache"), + XDG_STATE_HOME: path.posix.join(home, "state"), + }); +} diff --git a/packages/adapters/opencode-local/src/server/test.remote.test.ts b/packages/adapters/opencode-local/src/server/test.remote.test.ts index 2339baf6b1..aebe6cfa0a 100644 --- a/packages/adapters/opencode-local/src/server/test.remote.test.ts +++ b/packages/adapters/opencode-local/src/server/test.remote.test.ts @@ -74,6 +74,7 @@ vi.mock("@paperclipai/adapter-utils/execution-target", async () => { import { testEnvironment } from "./test.js"; describe("opencode remote environment diagnostics", () => { + const configHomes: string[] = []; let configHome: string; beforeEach(async () => { @@ -85,9 +86,12 @@ describe("opencode remote environment diagnostics", () => { vi.clearAllMocks(); vi.unstubAllEnvs(); await rm(configHome, { recursive: true, force: true }); + await Promise.all(configHomes.splice(0).map(dir => rm(dir, { recursive: true, force: true }))); }); - it("stages remote runtime config assets for sandbox hello probes", async () => { + it.each([false, true])("stages remote runtime config assets for sandbox hello probes (managed=%s)", async (managed) => { + const configHome = await mkdtemp(path.join(os.tmpdir(), "opencode-remote-test-config-")); + configHomes.push(configHome); const remoteTarget: AdapterExecutionTarget = { kind: "remote", transport: "sandbox", @@ -112,6 +116,13 @@ describe("opencode remote environment diagnostics", () => { config: { command: "opencode", model: "anthropic/claude-sonnet-4-5", + ...(managed ? { + managedAiConnection: { provider: "openrouter", method: "api_key" }, + } : {}), + env: { + XDG_CONFIG_HOME: configHome, + ...(managed ? { OPENAI_API_KEY: "", OPENROUTER_API_KEY: "fixture", HOME: "/var/folders/qa-managed", XDG_DATA_HOME: "/var/folders/qa-managed/data" } : {}), + }, }, executionTarget: remoteTarget, environmentName: "QA Cloudflare", @@ -134,6 +145,11 @@ describe("opencode remote environment diagnostics", () => { | [string, AdapterExecutionTarget, string, string[], { cwd: string; env: Record }] | undefined; expect(probeCall?.[4].cwd).toBe("/remote/workspace/.paperclip-runtime/runs/test/workspace"); + if (managed) { + expect(probeCall?.[4].env.HOME).toContain("/remote/workspace/.paperclip-runtime/runs/test/workspace/.paperclip-runtime/opencode/managed-auth/"); + expect(probeCall?.[4].env.XDG_DATA_HOME).toBe(`${probeCall?.[4].env.HOME}/data`); + expect(probeCall?.[4].env.XDG_CACHE_HOME).toBe(`${probeCall?.[4].env.HOME}/cache`); + } expect(probeCall?.[4].env.XDG_CONFIG_HOME).toBe( "/remote/workspace/.paperclip-runtime/runs/test/workspace/.paperclip-runtime/opencode/xdgConfig", ); diff --git a/packages/adapters/opencode-local/src/server/test.ts b/packages/adapters/opencode-local/src/server/test.ts index 634d920c13..d73f8c9413 100644 --- a/packages/adapters/opencode-local/src/server/test.ts +++ b/packages/adapters/opencode-local/src/server/test.ts @@ -28,7 +28,7 @@ import { import { discoverOpenCodeModels, ensureOpenCodeModelConfiguredAndAvailable } from "./models.js"; import { parseOpenCodeJsonl } from "./parse.js"; import { SANDBOX_INSTALL_COMMAND } from "../index.js"; -import { prepareOpenCodeRuntimeConfig } from "./runtime-config.js"; +import { prepareOpenCodeRuntimeConfig, prepareManagedOpenCodeRemoteHomes } from "./runtime-config.js"; function summarizeStatus(checks: AdapterEnvironmentCheck[]): AdapterEnvironmentTestResult["status"] { if (checks.some((check) => check.level === "error")) return "fail"; @@ -115,7 +115,7 @@ export async function testEnvironment( } const openaiKeyOverride = "OPENAI_API_KEY" in envConfig ? asString(envConfig.OPENAI_API_KEY, "") : null; - if (openaiKeyOverride !== null && openaiKeyOverride.trim() === "") { + if (!config.managedAiConnection && openaiKeyOverride !== null && openaiKeyOverride.trim() === "") { checks.push({ code: "opencode_openai_api_key_missing", level: "warn", @@ -172,6 +172,13 @@ export async function testEnvironment( if (localRuntimeConfigHome && preparedExecutionTargetRuntime.assetDirs.xdgConfig) { preparedRuntimeConfig.env.XDG_CONFIG_HOME = preparedExecutionTargetRuntime.assetDirs.xdgConfig; } + prepareManagedOpenCodeRemoteHomes({ + env: preparedRuntimeConfig.env, + config, + runtimeRootDir: preparedExecutionTargetRuntime.runtimeRootDir, + runId, + configDir: preparedExecutionTargetRuntime.assetDirs.xdgConfig, + }); } const runtimeEnv = normalizeEnv(ensurePathInEnv({ ...process.env, ...preparedRuntimeConfig.env })); diff --git a/packages/db/src/connections-v3-schema-core-migration.test.ts b/packages/db/src/connections-v3-schema-core-migration.test.ts index f24251323c..26d22c17d4 100644 --- a/packages/db/src/connections-v3-schema-core-migration.test.ts +++ b/packages/db/src/connections-v3-schema-core-migration.test.ts @@ -36,6 +36,9 @@ describeEmbeddedPostgres("connections v3 schema core migration", () => { await sql`ALTER TABLE "chat_endpoints" DROP CONSTRAINT "chat_endpoints_company_connection_fk"`; await sql`DELETE FROM "drizzle"."__drizzle_migrations" WHERE "hash" = ${await migrationHash()}`; + // AI defaults arrive in 0273 and depend on the composite grant key from + // 0232. Rewind that later table before recreating the 0182 grant schema. + await sql`DROP TABLE IF EXISTS "ai_connection_defaults"`; await sql`DROP TABLE IF EXISTS "connection_grant_delegations"`; await sql`DROP TABLE IF EXISTS "connection_grant_members"`; await sql`DROP TABLE IF EXISTS "connection_grants"`; diff --git a/packages/db/src/migrations/0275_easy_dragon_man.sql b/packages/db/src/migrations/0275_easy_dragon_man.sql new file mode 100644 index 0000000000..3a90f1660c --- /dev/null +++ b/packages/db/src/migrations/0275_easy_dragon_man.sql @@ -0,0 +1,5 @@ +ALTER TABLE "chat_endpoints" DROP CONSTRAINT IF EXISTS "chat_endpoints_provider_check";--> statement-breakpoint +ALTER TABLE "chat_external_principals" DROP CONSTRAINT IF EXISTS "chat_external_principals_provider_check";--> statement-breakpoint +CREATE UNIQUE INDEX IF NOT EXISTS "chat_endpoints_photon_number_uq" ON "chat_endpoints" USING btree ("bot_external_id") WHERE "chat_endpoints"."provider" = 'imessage-photon' and "chat_endpoints"."status" <> 'archived' and "chat_endpoints"."bot_external_id" is not null;--> statement-breakpoint +ALTER TABLE "chat_endpoints" ADD CONSTRAINT "chat_endpoints_provider_check" CHECK ("chat_endpoints"."provider" in ('slack', 'github', 'discord', 'microsoft-teams', 'telegram', 'agentmail', 'imessage-photon'));--> statement-breakpoint +ALTER TABLE "chat_external_principals" ADD CONSTRAINT "chat_external_principals_provider_check" CHECK ("chat_external_principals"."provider" in ('slack', 'github', 'discord', 'microsoft-teams', 'telegram', 'agentmail', 'imessage-photon')); \ No newline at end of file diff --git a/packages/db/src/migrations/0276_hard_mandroid.sql b/packages/db/src/migrations/0276_hard_mandroid.sql new file mode 100644 index 0000000000..dca093cfe0 --- /dev/null +++ b/packages/db/src/migrations/0276_hard_mandroid.sql @@ -0,0 +1,77 @@ +CREATE TABLE IF NOT EXISTS "ai_connection_defaults" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "company_id" uuid NOT NULL, + "user_id" text NOT NULL, + "provider" text NOT NULL, + "method" text NOT NULL, + "grant_id" uuid, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "ai_connection_defaults_provider_check" CHECK ("ai_connection_defaults"."provider" in ('anthropic','openai','openrouter','xai')), + CONSTRAINT "ai_connection_defaults_method_check" CHECK ("ai_connection_defaults"."method" in ('subscription','api_key')) +); +--> statement-breakpoint +ALTER TABLE "tool_connections" DROP CONSTRAINT IF EXISTS "tool_connections_transport_check";--> statement-breakpoint +ALTER TABLE "tool_connections" DROP CONSTRAINT IF EXISTS "tool_connections_purpose_check";--> statement-breakpoint +ALTER TABLE "tool_connections" DROP CONSTRAINT IF EXISTS "tool_connections_channel_transport_check";--> statement-breakpoint +ALTER TABLE "adapter_auth_sessions" ADD COLUMN IF NOT EXISTS "ai_connection" jsonb;--> statement-breakpoint +ALTER TABLE "adapter_auth_sessions" ADD COLUMN IF NOT EXISTS "connection_id" uuid;--> statement-breakpoint +ALTER TABLE "adapter_auth_sessions" ADD COLUMN IF NOT EXISTS "connection_grant_id" uuid;--> statement-breakpoint +ALTER TABLE "adapter_auth_sessions" ADD COLUMN IF NOT EXISTS "connection_method" text;--> statement-breakpoint +DO $$ BEGIN ALTER TABLE "ai_connection_defaults" ADD CONSTRAINT "ai_connection_defaults_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN NULL; END $$;--> statement-breakpoint +DO $$ BEGIN ALTER TABLE "ai_connection_defaults" ADD CONSTRAINT "ai_connection_defaults_company_grant_fk" FOREIGN KEY ("company_id","grant_id") REFERENCES "public"."connection_grants"("company_id","id") ON DELETE no action ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN NULL; END $$;--> statement-breakpoint +CREATE UNIQUE INDEX IF NOT EXISTS "ai_connection_defaults_owner_method_uq" ON "ai_connection_defaults" USING btree ("company_id","user_id","provider","method");--> statement-breakpoint +ALTER TABLE "tool_connections" ADD CONSTRAINT "tool_connections_transport_check" CHECK ("tool_connections"."transport" in ('mcp_remote', 'rest_api', 'local_stdio', 'chat_sdk', 'runtime_auth'));--> statement-breakpoint +ALTER TABLE "tool_connections" ADD CONSTRAINT "tool_connections_purpose_check" CHECK ("tool_connections"."connection_purpose" in ('tool', 'channel', 'ai'));--> statement-breakpoint +ALTER TABLE "tool_connections" ADD CONSTRAINT "tool_connections_channel_transport_check" CHECK (( + ("tool_connections"."connection_purpose" = 'tool' and "tool_connections"."transport" not in ('chat_sdk', 'runtime_auth')) + or + ("tool_connections"."connection_purpose" = 'channel' and ("tool_connections"."transport" = 'chat_sdk' or ("tool_connections"."transport" = 'rest_api' and "tool_connections"."config"->>'provider' = 'agentmail'))) + or + ("tool_connections"."connection_purpose" = 'ai' and "tool_connections"."transport" = 'runtime_auth') + ));--> statement-breakpoint +-- Only declared per-user credentials have reliable ownership. Host auth homes +-- and company secrets are deliberately left untouched. No agent binding changes. +DO $$ +DECLARE candidate record; application_id uuid; connection_id uuid; grant_id uuid; +BEGIN + FOR candidate IN + SELECT DISTINCT s.id AS secret_id, s.company_id, s.owner_user_id, s.user_secret_definition_id, + s.name, s.created_at, a.id AS agent_id, + CASE d.env_key WHEN 'ANTHROPIC_API_KEY' THEN 'anthropic' WHEN 'CLAUDE_CODE_OAUTH_TOKEN' THEN 'anthropic' + WHEN 'OPENAI_API_KEY' THEN 'openai' WHEN 'OPENROUTER_API_KEY' THEN 'openrouter' WHEN 'XAI_API_KEY' THEN 'xai' END AS provider, + CASE WHEN d.env_key = 'CLAUDE_CODE_OAUTH_TOKEN' THEN 'subscription' ELSE 'api_key' END AS method + FROM company_secrets s + JOIN user_secret_declarations d ON d.company_id = s.company_id AND d.user_secret_definition_id = s.user_secret_definition_id + JOIN agents a ON a.company_id = s.company_id AND a.id::text = d.target_id AND d.target_type = 'agent' + WHERE s.scope = 'user' AND s.owner_user_id IS NOT NULL AND s.status = 'active' AND s.deleted_at IS NULL + AND ((a.adapter_type = 'claude_local' AND d.env_key IN ('ANTHROPIC_API_KEY','CLAUDE_CODE_OAUTH_TOKEN')) + OR (a.adapter_type = 'codex_local' AND d.env_key = 'OPENAI_API_KEY') + OR (a.adapter_type = 'opencode_local' AND d.env_key = 'OPENROUTER_API_KEY') + OR (a.adapter_type = 'grok_local' AND d.env_key = 'XAI_API_KEY')) + ORDER BY s.created_at + LOOP + connection_id := overlay(overlay(md5('ai-connection:' || candidate.secret_id::text || ':' || candidate.provider || ':' || candidate.method) placing '5' from 13 for 1) placing '8' from 17 for 1)::uuid; + grant_id := overlay(overlay(md5('ai-grant:' || connection_id::text) placing '5' from 13 for 1) placing '8' from 17 for 1)::uuid; + INSERT INTO tool_applications(company_id,application_key,name,type,owner_user_id,metadata) + VALUES(candidate.company_id,'app-gallery:' || candidate.provider, + CASE candidate.provider WHEN 'anthropic' THEN 'Claude' WHEN 'openai' THEN 'OpenAI' WHEN 'openrouter' THEN 'OpenRouter' ELSE 'Grok' END, + 'mcp_http',candidate.owner_user_id,jsonb_build_object('sourceTemplateKey',candidate.provider)) ON CONFLICT DO NOTHING; + SELECT id INTO application_id FROM tool_applications WHERE company_id = candidate.company_id + AND (application_key = 'app-gallery:' || candidate.provider OR name = CASE candidate.provider WHEN 'anthropic' THEN 'Claude' WHEN 'openai' THEN 'OpenAI' WHEN 'openrouter' THEN 'OpenRouter' ELSE 'Grok' END) LIMIT 1; + INSERT INTO tool_connections(id,company_id,application_id,name,uid,connection_purpose,transport,auth_kind,credential_policy,status,enabled,health_status,config,created_by_user_id) + VALUES(connection_id,candidate.company_id,application_id,candidate.name,'ai-' || connection_id::text,'ai','runtime_auth', + CASE WHEN candidate.method = 'subscription' THEN 'oauth' ELSE 'api_key' END,'per_user','active',true,'unknown', + jsonb_build_object('sourceTemplateKey',candidate.provider,'ai',jsonb_build_object('provider',candidate.provider,'method',candidate.method),'aiLegacyAdoption',true),candidate.owner_user_id) + ON CONFLICT DO NOTHING; + INSERT INTO connection_grants(id,company_id,connection_id,kind,subject_user_id,credential_secret_refs,created_by_user_id) + VALUES(grant_id,candidate.company_id,connection_id,'user',candidate.owner_user_id, + jsonb_build_array(jsonb_build_object('secretId',candidate.secret_id,'configPath','ai.credential','required',true,'versionSelector','latest')),candidate.owner_user_id) + ON CONFLICT DO NOTHING; + INSERT INTO user_secret_declarations(company_id,user_secret_definition_id,target_type,target_id,config_path,env_key) + VALUES(candidate.company_id,candidate.user_secret_definition_id,'tool_connection',connection_id::text,'ai.credential','ai.credential') ON CONFLICT DO NOTHING; + INSERT INTO ai_connection_defaults(company_id,user_id,provider,method,grant_id) + VALUES(candidate.company_id,candidate.owner_user_id,candidate.provider,candidate.method,grant_id) ON CONFLICT DO NOTHING; + INSERT INTO tool_connection_installs(company_id,connection_id,target_type,target_id,created_by_user_id) + VALUES(candidate.company_id,connection_id,'agent',candidate.agent_id::text,candidate.owner_user_id) ON CONFLICT DO NOTHING; + END LOOP; +END $$; diff --git a/packages/db/src/migrations/0275_sandbox_work_folders.sql b/packages/db/src/migrations/0277_sandbox_work_folders.sql similarity index 100% rename from packages/db/src/migrations/0275_sandbox_work_folders.sql rename to packages/db/src/migrations/0277_sandbox_work_folders.sql diff --git a/packages/db/src/migrations/meta/0275_snapshot.json b/packages/db/src/migrations/meta/0275_snapshot.json index f037a79b73..08ebeb296b 100644 --- a/packages/db/src/migrations/meta/0275_snapshot.json +++ b/packages/db/src/migrations/meta/0275_snapshot.json @@ -1,5 +1,5 @@ { - "id": "91aa55be-5067-4db5-b8e5-1eb670910029", + "id": "d1a3e2cb-5f5f-4bb2-98ec-8475b445d6e6", "prevId": "02bc1b0f-2d2b-4c3e-9320-b085bade9f09", "version": "7", "dialect": "postgresql", @@ -6505,6 +6505,22 @@ "method": "btree", "with": {} }, + "chat_endpoints_photon_number_uq": { + "name": "chat_endpoints_photon_number_uq", + "columns": [ + { + "expression": "bot_external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat_endpoints\".\"provider\" = 'imessage-photon' and \"chat_endpoints\".\"status\" <> 'archived' and \"chat_endpoints\".\"bot_external_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, "chat_endpoints_live_discord_bot_external_uq": { "name": "chat_endpoints_live_discord_bot_external_uq", "columns": [ @@ -6663,7 +6679,7 @@ }, "chat_endpoints_provider_check": { "name": "chat_endpoints_provider_check", - "value": "\"chat_endpoints\".\"provider\" in ('slack', 'github', 'discord', 'microsoft-teams', 'telegram', 'agentmail')" + "value": "\"chat_endpoints\".\"provider\" in ('slack', 'github', 'discord', 'microsoft-teams', 'telegram', 'agentmail', 'imessage-photon')" }, "chat_endpoints_status_check": { "name": "chat_endpoints_status_check", @@ -6848,7 +6864,7 @@ "checkConstraints": { "chat_external_principals_provider_check": { "name": "chat_external_principals_provider_check", - "value": "\"chat_external_principals\".\"provider\" in ('slack', 'github', 'discord', 'microsoft-teams', 'telegram', 'agentmail')" + "value": "\"chat_external_principals\".\"provider\" in ('slack', 'github', 'discord', 'microsoft-teams', 'telegram', 'agentmail', 'imessage-photon')" }, "chat_external_principals_kind_check": { "name": "chat_external_principals_kind_check", @@ -47434,655 +47450,6 @@ "policies": {}, "checkConstraints": {}, "isRLSEnabled": false - }, - "public.task_repository_bindings": { - "name": "task_repository_bindings", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "company_id": { - "name": "company_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "task_id": { - "name": "task_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "workspace_id": { - "name": "workspace_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "repo_url": { - "name": "repo_url", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "repo_ref": { - "name": "repo_ref", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "setup_complete": { - "name": "setup_complete", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "retired_at": { - "name": "retired_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "checkpoint_key": { - "name": "checkpoint_key", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "checkpoint_sha256": { - "name": "checkpoint_sha256", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "checkpoint_at": { - "name": "checkpoint_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": {}, - "foreignKeys": { - "task_repository_bindings_company_id_companies_id_fk": { - "name": "task_repository_bindings_company_id_companies_id_fk", - "tableFrom": "task_repository_bindings", - "tableTo": "companies", - "columnsFrom": [ - "company_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "task_repository_bindings_task_id_issues_id_fk": { - "name": "task_repository_bindings_task_id_issues_id_fk", - "tableFrom": "task_repository_bindings", - "tableTo": "issues", - "columnsFrom": [ - "task_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "task_repository_bindings_workspace_uq": { - "name": "task_repository_bindings_workspace_uq", - "nullsNotDistinct": false, - "columns": [ - "company_id", - "task_id", - "workspace_id" - ] - }, - "task_repository_bindings_name_uq": { - "name": "task_repository_bindings_name_uq", - "nullsNotDistinct": false, - "columns": [ - "company_id", - "task_id", - "name" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.work_file_operations": { - "name": "work_file_operations", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "company_id": { - "name": "company_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "folder_id": { - "name": "folder_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "operation_id": { - "name": "operation_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "fingerprint": { - "name": "fingerprint", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "work_file_operations_company_id_folder_id_work_folders_company_id_id_fk": { - "name": "work_file_operations_company_id_folder_id_work_folders_company_id_id_fk", - "tableFrom": "work_file_operations", - "tableTo": "work_folders", - "columnsFrom": [ - "company_id", - "folder_id" - ], - "columnsTo": [ - "company_id", - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "work_file_operations_receipt_uq": { - "name": "work_file_operations_receipt_uq", - "nullsNotDistinct": false, - "columns": [ - "folder_id", - "operation_id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.work_files": { - "name": "work_files", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "company_id": { - "name": "company_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "folder_id": { - "name": "folder_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "path": { - "name": "path", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "kind": { - "name": "kind", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'file'" - }, - "object_key": { - "name": "object_key", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "byte_size": { - "name": "byte_size", - "type": "bigint", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "sha256": { - "name": "sha256", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "content_type": { - "name": "content_type", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'application/octet-stream'" - }, - "executable": { - "name": "executable", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "deleted_at": { - "name": "deleted_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "work_files_folder_path_uq": { - "name": "work_files_folder_path_uq", - "columns": [ - { - "expression": "folder_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "path", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "\"work_files\".\"deleted_at\" is null", - "concurrently": false, - "method": "btree", - "with": {} - }, - "work_files_company_folder_idx": { - "name": "work_files_company_folder_idx", - "columns": [ - { - "expression": "company_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "folder_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "work_files_company_id_folder_id_work_folders_company_id_id_fk": { - "name": "work_files_company_id_folder_id_work_folders_company_id_id_fk", - "tableFrom": "work_files", - "tableTo": "work_folders", - "columnsFrom": [ - "company_id", - "folder_id" - ], - "columnsTo": [ - "company_id", - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.work_folder_objects": { - "name": "work_folder_objects", - "schema": "", - "columns": { - "object_key": { - "name": "object_key", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "company_id": { - "name": "company_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "folder_id": { - "name": "folder_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - }, - "repository_binding_id": { - "name": "repository_binding_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - }, - "provider": { - "name": "provider", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "delete_after": { - "name": "delete_after", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "work_folder_objects_cleanup_idx": { - "name": "work_folder_objects_cleanup_idx", - "columns": [ - { - "expression": "provider", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "delete_after", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.work_folder_runs": { - "name": "work_folder_runs", - "schema": "", - "columns": { - "run_id": { - "name": "run_id", - "type": "uuid", - "primaryKey": true, - "notNull": true - }, - "company_id": { - "name": "company_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "manifest": { - "name": "manifest", - "type": "jsonb", - "primaryKey": false, - "notNull": true - }, - "baselines": { - "name": "baselines", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'{}'::jsonb" - }, - "pending_operations": { - "name": "pending_operations", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'{}'::jsonb" - }, - "state": { - "name": "state", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'starting'" - }, - "last_saved_at": { - "name": "last_saved_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "error": { - "name": "error", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "refresh_requested": { - "name": "refresh_requested", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "work_folder_runs_company_idx": { - "name": "work_folder_runs_company_idx", - "columns": [ - { - "expression": "company_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "work_folder_runs_run_id_heartbeat_runs_id_fk": { - "name": "work_folder_runs_run_id_heartbeat_runs_id_fk", - "tableFrom": "work_folder_runs", - "tableTo": "heartbeat_runs", - "columnsFrom": [ - "run_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "work_folder_runs_company_id_companies_id_fk": { - "name": "work_folder_runs_company_id_companies_id_fk", - "tableFrom": "work_folder_runs", - "tableTo": "companies", - "columnsFrom": [ - "company_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.work_folders": { - "name": "work_folders", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "company_id": { - "name": "company_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "scope": { - "name": "scope", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "owner_id": { - "name": "owner_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "imported_at": { - "name": "imported_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": {}, - "foreignKeys": { - "work_folders_company_id_companies_id_fk": { - "name": "work_folders_company_id_companies_id_fk", - "tableFrom": "work_folders", - "tableTo": "companies", - "columnsFrom": [ - "company_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "work_folders_owner_uq": { - "name": "work_folders_owner_uq", - "nullsNotDistinct": false, - "columns": [ - "company_id", - "scope", - "owner_id" - ] - }, - "work_folders_company_id_uq": { - "name": "work_folders_company_id_uq", - "nullsNotDistinct": false, - "columns": [ - "company_id", - "id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false } }, "enums": {}, diff --git a/packages/db/src/migrations/meta/0276_snapshot.json b/packages/db/src/migrations/meta/0276_snapshot.json new file mode 100644 index 0000000000..644bf78d42 --- /dev/null +++ b/packages/db/src/migrations/meta/0276_snapshot.json @@ -0,0 +1,47630 @@ +{ + "id": "9c7c9e05-e663-4a29-8b46-63733f62da9b", + "prevId": "d1a3e2cb-5f5f-4bb2-98ec-8475b445d6e6", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.activity_log": { + "name": "activity_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "activity_log_company_created_idx": { + "name": "activity_log_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "activity_log_company_agent_created_idx": { + "name": "activity_log_company_agent_created_idx", + "columns": [ + { + "expression": "company_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": {} + }, + "activity_log_company_responsible_user_created_idx": { + "name": "activity_log_company_responsible_user_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "responsible_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "activity_log_run_id_idx": { + "name": "activity_log_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "activity_log_entity_type_id_idx": { + "name": "activity_log_entity_type_id_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "activity_log_company_id_companies_id_fk": { + "name": "activity_log_company_id_companies_id_fk", + "tableFrom": "activity_log", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "activity_log_agent_id_agents_id_fk": { + "name": "activity_log_agent_id_agents_id_fk", + "tableFrom": "activity_log", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "activity_log_run_id_heartbeat_runs_id_fk": { + "name": "activity_log_run_id_heartbeat_runs_id_fk", + "tableFrom": "activity_log", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.adapter_auth_sessions": { + "name": "adapter_auth_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "environment_id": { + "name": "environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "ai_connection": { + "name": "ai_connection", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_grant_id": { + "name": "connection_grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_method": { + "name": "connection_method", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_by_user_id": { + "name": "started_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public_session_id": { + "name": "public_session_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "provider_lease_id": { + "name": "provider_lease_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'starting'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "promotion_expires_at": { + "name": "promotion_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "bound_at": { + "name": "bound_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_claim": { + "name": "result_claim", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "adapter_auth_sessions_company_status_idx": { + "name": "adapter_auth_sessions_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "adapter_auth_sessions_company_owner_adapter_active_uq": { + "name": "adapter_auth_sessions_company_owner_adapter_active_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "adapter_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"adapter_auth_sessions\".\"status\" IN ('starting', 'waiting_for_user', 'promoting', 'awaiting_code', 'submitting')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "adapter_auth_sessions_public_session_id_uq": { + "name": "adapter_auth_sessions_public_session_id_uq", + "columns": [ + { + "expression": "public_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "adapter_auth_sessions_environment_idx": { + "name": "adapter_auth_sessions_environment_idx", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "adapter_auth_sessions_expires_idx": { + "name": "adapter_auth_sessions_expires_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "adapter_auth_sessions_provider_lease_idx": { + "name": "adapter_auth_sessions_provider_lease_idx", + "columns": [ + { + "expression": "provider_lease_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "adapter_auth_sessions_company_id_companies_id_fk": { + "name": "adapter_auth_sessions_company_id_companies_id_fk", + "tableFrom": "adapter_auth_sessions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "adapter_auth_sessions_environment_id_environments_id_fk": { + "name": "adapter_auth_sessions_environment_id_environments_id_fk", + "tableFrom": "adapter_auth_sessions", + "tableTo": "environments", + "columnsFrom": [ + "environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_api_keys": { + "name": "agent_api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope_config": { + "name": "scope_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "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 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_api_keys_key_hash_idx": { + "name": "agent_api_keys_key_hash_idx", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_api_keys_company_agent_idx": { + "name": "agent_api_keys_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_api_keys_agent_id_agents_id_fk": { + "name": "agent_api_keys_agent_id_agents_id_fk", + "tableFrom": "agent_api_keys", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_api_keys_company_id_companies_id_fk": { + "name": "agent_api_keys_company_id_companies_id_fk", + "tableFrom": "agent_api_keys", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_config_revisions": { + "name": "agent_config_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'patch'" + }, + "rolled_back_from_revision_id": { + "name": "rolled_back_from_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "changed_keys": { + "name": "changed_keys", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "before_config": { + "name": "before_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "after_config": { + "name": "after_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_config_revisions_company_agent_created_idx": { + "name": "agent_config_revisions_company_agent_created_idx", + "columns": [ + { + "expression": "company_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": {} + }, + "agent_config_revisions_agent_created_idx": { + "name": "agent_config_revisions_agent_created_idx", + "columns": [ + { + "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_config_revisions_company_id_companies_id_fk": { + "name": "agent_config_revisions_company_id_companies_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_config_revisions_agent_id_agents_id_fk": { + "name": "agent_config_revisions_agent_id_agents_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_config_revisions_created_by_agent_id_agents_id_fk": { + "name": "agent_config_revisions_created_by_agent_id_agents_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_memberships": { + "name": "agent_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'joined'" + }, + "starred_at": { + "name": "starred_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": { + "agent_memberships_company_user_idx": { + "name": "agent_memberships_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_memberships_company_user_starred_idx": { + "name": "agent_memberships_company_user_starred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "starred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_memberships_agent_idx": { + "name": "agent_memberships_agent_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_memberships_company_user_agent_uq": { + "name": "agent_memberships_company_user_agent_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_memberships_company_id_companies_id_fk": { + "name": "agent_memberships_company_id_companies_id_fk", + "tableFrom": "agent_memberships", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_memberships_agent_id_agents_id_fk": { + "name": "agent_memberships_agent_id_agents_id_fk", + "tableFrom": "agent_memberships", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_runtime_state": { + "name": "agent_runtime_state", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state_json": { + "name": "state_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_run_id": { + "name": "last_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_run_status": { + "name": "last_run_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "total_input_tokens": { + "name": "total_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_output_tokens": { + "name": "total_output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cached_input_tokens": { + "name": "total_cached_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cost_cents": { + "name": "total_cost_cents", + "type": "bigint", + "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": { + "agent_runtime_state_company_agent_idx": { + "name": "agent_runtime_state_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_runtime_state_company_updated_idx": { + "name": "agent_runtime_state_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_runtime_state_agent_id_agents_id_fk": { + "name": "agent_runtime_state_agent_id_agents_id_fk", + "tableFrom": "agent_runtime_state", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_runtime_state_company_id_companies_id_fk": { + "name": "agent_runtime_state_company_id_companies_id_fk", + "tableFrom": "agent_runtime_state", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_session_goal_actions": { + "name": "agent_session_goal_actions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload_json": { + "name": "payload_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "result_json": { + "name": "result_json", + "type": "jsonb", + "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()" + }, + "delivered_at": { + "name": "delivered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_session_goal_actions_session_request_uniq": { + "name": "agent_session_goal_actions_session_request_uniq", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_session_goal_actions_company_status_created_idx": { + "name": "agent_session_goal_actions_company_status_created_idx", + "columns": [ + { + "expression": "company_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": {} + }, + "agent_session_goal_actions_session_created_idx": { + "name": "agent_session_goal_actions_session_created_idx", + "columns": [ + { + "expression": "session_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_session_goal_actions_company_id_companies_id_fk": { + "name": "agent_session_goal_actions_company_id_companies_id_fk", + "tableFrom": "agent_session_goal_actions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_session_goal_actions_session_id_agent_task_sessions_id_fk": { + "name": "agent_session_goal_actions_session_id_agent_task_sessions_id_fk", + "tableFrom": "agent_session_goal_actions", + "tableTo": "agent_task_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_task_sessions": { + "name": "agent_task_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "task_key": { + "name": "task_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_params_json": { + "name": "session_params_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "session_display_id": { + "name": "session_display_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_run_id": { + "name": "last_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "goal_capability_json": { + "name": "goal_capability_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "goal_json": { + "name": "goal_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "goal_status": { + "name": "goal_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "goal_desired_state": { + "name": "goal_desired_state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "goal_source_id": { + "name": "goal_source_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "goal_source_cursor": { + "name": "goal_source_cursor", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "goal_revision": { + "name": "goal_revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "goal_observed_at": { + "name": "goal_observed_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": { + "agent_task_sessions_company_agent_adapter_task_uniq": { + "name": "agent_task_sessions_company_agent_adapter_task_uniq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "adapter_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "task_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_task_sessions_company_agent_updated_idx": { + "name": "agent_task_sessions_company_agent_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_task_sessions_company_task_updated_idx": { + "name": "agent_task_sessions_company_task_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "task_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_task_sessions_company_id_companies_id_fk": { + "name": "agent_task_sessions_company_id_companies_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_task_sessions_agent_id_agents_id_fk": { + "name": "agent_task_sessions_agent_id_agents_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_task_sessions_last_run_id_heartbeat_runs_id_fk": { + "name": "agent_task_sessions_last_run_id_heartbeat_runs_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "last_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_wakeup_requests": { + "name": "agent_wakeup_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trigger_detail": { + "name": "trigger_detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "coalesced_count": { + "name": "coalesced_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "requested_by_actor_type": { + "name": "requested_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_by_actor_id": { + "name": "requested_by_actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_at": { + "name": "requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_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": { + "agent_wakeup_requests_company_agent_status_idx": { + "name": "agent_wakeup_requests_company_agent_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_company_requested_idx": { + "name": "agent_wakeup_requests_company_requested_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_agent_requested_idx": { + "name": "agent_wakeup_requests_agent_requested_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_review_path_recovery_idempotency_uq": { + "name": "agent_wakeup_requests_review_path_recovery_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_wakeup_requests\".\"idempotency_key\" LIKE 'issue_review_path_lost:%' AND \"agent_wakeup_requests\".\"status\" <> 'skipped'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_disposition_repair_idempotency_uq": { + "name": "agent_wakeup_requests_disposition_repair_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_wakeup_requests\".\"idempotency_key\" LIKE 'issue_disposition_repair:%' AND \"agent_wakeup_requests\".\"status\" <> 'skipped'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_question_response_delivery_idempotency_uq": { + "name": "agent_wakeup_requests_question_response_delivery_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "(\"agent_wakeup_requests\".\"idempotency_key\" LIKE 'question-response:%' OR \"agent_wakeup_requests\".\"idempotency_key\" LIKE 'interaction:%') AND \"agent_wakeup_requests\".\"status\" NOT IN ('skipped', 'failed', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_connection_intent_delivery_idempotency_uq": { + "name": "agent_wakeup_requests_connection_intent_delivery_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_wakeup_requests\".\"idempotency_key\" LIKE 'connection-intent:%' AND \"agent_wakeup_requests\".\"status\" NOT IN ('skipped', 'failed', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_tool_action_delivery_uq": { + "name": "agent_wakeup_requests_tool_action_delivery_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_wakeup_requests\".\"idempotency_key\" LIKE 'tool-action-response:%' AND \"agent_wakeup_requests\".\"status\" NOT IN ('skipped', 'failed', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_company_payload_issue_idx": { + "name": "agent_wakeup_requests_company_payload_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "(\"payload\" ->> 'issueId')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_wakeup_requests_company_id_companies_id_fk": { + "name": "agent_wakeup_requests_company_id_companies_id_fk", + "tableFrom": "agent_wakeup_requests", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_wakeup_requests_agent_id_agents_id_fk": { + "name": "agent_wakeup_requests_agent_id_agents_id_fk", + "tableFrom": "agent_wakeup_requests", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agents": { + "name": "agents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'general'" + }, + "title": { + "name": "title", + "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": "'idle'" + }, + "reports_to": { + "name": "reports_to", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'process'" + }, + "adapter_config": { + "name": "adapter_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "runtime_config": { + "name": "runtime_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "default_environment_id": { + "name": "default_environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "budget_monthly_cents": { + "name": "budget_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "spent_monthly_cents": { + "name": "spent_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_reason": { + "name": "error_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_heartbeat_at": { + "name": "last_heartbeat_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agents_company_status_idx": { + "name": "agents_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_company_reports_to_idx": { + "name": "agents_company_reports_to_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "reports_to", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_company_default_environment_idx": { + "name": "agents_company_default_environment_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "default_environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agents_company_id_companies_id_fk": { + "name": "agents_company_id_companies_id_fk", + "tableFrom": "agents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agents_reports_to_agents_id_fk": { + "name": "agents_reports_to_agents_id_fk", + "tableFrom": "agents", + "tableTo": "agents", + "columnsFrom": [ + "reports_to" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agents_default_environment_id_environments_id_fk": { + "name": "agents_default_environment_id_environments_id_fk", + "tableFrom": "agents", + "tableTo": "environments", + "columnsFrom": [ + "default_environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "agents_company_id_uq": { + "name": "agents_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_connection_defaults": { + "name": "ai_connection_defaults", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "method": { + "name": "method", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ai_connection_defaults_owner_method_uq": { + "name": "ai_connection_defaults_owner_method_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "method", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_connection_defaults_company_id_companies_id_fk": { + "name": "ai_connection_defaults_company_id_companies_id_fk", + "tableFrom": "ai_connection_defaults", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_connection_defaults_company_grant_fk": { + "name": "ai_connection_defaults_company_grant_fk", + "tableFrom": "ai_connection_defaults", + "tableTo": "connection_grants", + "columnsFrom": [ + "company_id", + "grant_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "ai_connection_defaults_provider_check": { + "name": "ai_connection_defaults_provider_check", + "value": "\"ai_connection_defaults\".\"provider\" in ('anthropic','openai','openrouter','xai')" + }, + "ai_connection_defaults_method_check": { + "name": "ai_connection_defaults_method_check", + "value": "\"ai_connection_defaults\".\"method\" in ('subscription','api_key')" + } + }, + "isRLSEnabled": false + }, + "public.approval_comments": { + "name": "approval_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "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": { + "approval_comments_company_idx": { + "name": "approval_comments_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "approval_comments_approval_idx": { + "name": "approval_comments_approval_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "approval_comments_approval_created_idx": { + "name": "approval_comments_approval_created_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "approval_comments_company_id_companies_id_fk": { + "name": "approval_comments_company_id_companies_id_fk", + "tableFrom": "approval_comments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approval_comments_approval_id_approvals_id_fk": { + "name": "approval_comments_approval_id_approvals_id_fk", + "tableFrom": "approval_comments", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approval_comments_author_agent_id_agents_id_fk": { + "name": "approval_comments_author_agent_id_agents_id_fk", + "tableFrom": "approval_comments", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_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, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requested_by_agent_id": { + "name": "requested_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_by_user_id": { + "name": "requested_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "decision_note": { + "name": "decision_note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_by_user_id": { + "name": "decided_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_at": { + "name": "decided_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": { + "approvals_company_status_type_idx": { + "name": "approvals_company_status_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "approvals_company_id_companies_id_fk": { + "name": "approvals_company_id_companies_id_fk", + "tableFrom": "approvals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approvals_requested_by_agent_id_agents_id_fk": { + "name": "approvals_requested_by_agent_id_agents_id_fk", + "tableFrom": "approvals", + "tableTo": "agents", + "columnsFrom": [ + "requested_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.assets": { + "name": "assets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "object_key": { + "name": "object_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "byte_size": { + "name": "byte_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "sha256": { + "name": "sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "original_filename": { + "name": "original_filename", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "assets_company_created_idx": { + "name": "assets_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "assets_company_provider_idx": { + "name": "assets_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "assets_company_object_key_uq": { + "name": "assets_company_object_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "assets_company_id_companies_id_fk": { + "name": "assets_company_id_companies_id_fk", + "tableFrom": "assets", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "assets_created_by_agent_id_agents_id_fk": { + "name": "assets_created_by_agent_id_agents_id_fk", + "tableFrom": "assets", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "issuer": { + "name": "issuer", + "type": "text", + "primaryKey": false, + "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 + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "account_issuer_account_id_uq": { + "name": "account_issuer_account_id_uq", + "columns": [ + { + "expression": "issuer", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_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 + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.board_api_keys": { + "name": "board_api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "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 + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "board_api_keys_key_hash_idx": { + "name": "board_api_keys_key_hash_idx", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "board_api_keys_user_idx": { + "name": "board_api_keys_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "board_api_keys_user_id_user_id_fk": { + "name": "board_api_keys_user_id_user_id_fk", + "tableFrom": "board_api_keys", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.budget_incidents": { + "name": "budget_incidents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "policy_id": { + "name": "policy_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_kind": { + "name": "window_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_start": { + "name": "window_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "window_end": { + "name": "window_end", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "threshold_type": { + "name": "threshold_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount_limit": { + "name": "amount_limit", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "amount_observed": { + "name": "amount_observed", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "budget_incidents_company_status_idx": { + "name": "budget_incidents_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_incidents_company_scope_idx": { + "name": "budget_incidents_company_scope_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_incidents_policy_window_threshold_idx": { + "name": "budget_incidents_policy_window_threshold_idx", + "columns": [ + { + "expression": "policy_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_start", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "threshold_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"budget_incidents\".\"status\" <> 'dismissed'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "budget_incidents_company_id_companies_id_fk": { + "name": "budget_incidents_company_id_companies_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "budget_incidents_policy_id_budget_policies_id_fk": { + "name": "budget_incidents_policy_id_budget_policies_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "budget_policies", + "columnsFrom": [ + "policy_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "budget_incidents_approval_id_approvals_id_fk": { + "name": "budget_incidents_approval_id_approvals_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.budget_policies": { + "name": "budget_policies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'billed_cents'" + }, + "window_kind": { + "name": "window_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "warn_percent": { + "name": "warn_percent", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 80 + }, + "hard_stop_enabled": { + "name": "hard_stop_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "notify_enabled": { + "name": "notify_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "budget_policies_company_scope_active_idx": { + "name": "budget_policies_company_scope_active_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_policies_company_window_idx": { + "name": "budget_policies_company_window_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_policies_company_scope_metric_unique_idx": { + "name": "budget_policies_company_scope_metric_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "budget_policies_company_id_companies_id_fk": { + "name": "budget_policies_company_id_companies_id_fk", + "tableFrom": "budget_policies", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.built_in_managed_resources": { + "name": "built_in_managed_resources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "bundle_key": { + "name": "bundle_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_kind": { + "name": "resource_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_key": { + "name": "resource_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "stock_version": { + "name": "stock_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stock_hash": { + "name": "stock_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "defaults_json": { + "name": "defaults_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "built_in_managed_resources_company_idx": { + "name": "built_in_managed_resources_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "built_in_managed_resources_resource_idx": { + "name": "built_in_managed_resources_resource_idx", + "columns": [ + { + "expression": "resource_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "built_in_managed_resources_company_bundle_resource_uq": { + "name": "built_in_managed_resources_company_bundle_resource_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bundle_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "built_in_managed_resources_company_id_companies_id_fk": { + "name": "built_in_managed_resources_company_id_companies_id_fk", + "tableFrom": "built_in_managed_resources", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.case_attachments": { + "name": "case_attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_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": { + "case_attachments_company_case_idx": { + "name": "case_attachments_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_attachments_asset_uq": { + "name": "case_attachments_asset_uq", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "case_attachments_company_id_companies_id_fk": { + "name": "case_attachments_company_id_companies_id_fk", + "tableFrom": "case_attachments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_attachments_case_id_cases_id_fk": { + "name": "case_attachments_case_id_cases_id_fk", + "tableFrom": "case_attachments", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_attachments_asset_id_assets_id_fk": { + "name": "case_attachments_asset_id_assets_id_fk", + "tableFrom": "case_attachments", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.case_documents": { + "name": "case_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "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": { + "case_documents_company_case_key_uq": { + "name": "case_documents_company_case_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_documents_document_uq": { + "name": "case_documents_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_documents_company_case_updated_idx": { + "name": "case_documents_company_case_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "case_documents_company_id_companies_id_fk": { + "name": "case_documents_company_id_companies_id_fk", + "tableFrom": "case_documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_documents_case_id_cases_id_fk": { + "name": "case_documents_case_id_cases_id_fk", + "tableFrom": "case_documents", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_documents_document_id_documents_id_fk": { + "name": "case_documents_document_id_documents_id_fk", + "tableFrom": "case_documents", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.case_events": { + "name": "case_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_agent_id": { + "name": "actor_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "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()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "case_events_case_created_idx": { + "name": "case_events_case_created_idx", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_events_company_case_idx": { + "name": "case_events_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "case_events_company_id_companies_id_fk": { + "name": "case_events_company_id_companies_id_fk", + "tableFrom": "case_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_events_case_id_cases_id_fk": { + "name": "case_events_case_id_cases_id_fk", + "tableFrom": "case_events", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_events_actor_agent_id_agents_id_fk": { + "name": "case_events_actor_agent_id_agents_id_fk", + "tableFrom": "case_events", + "tableTo": "agents", + "columnsFrom": [ + "actor_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "case_events_kind_check": { + "name": "case_events_kind_check", + "value": "\"case_events\".\"kind\" in (\n 'created',\n 'updated',\n 'fields_changed',\n 'status_changed',\n 'issue_linked',\n 'issue_unlinked',\n 'document_revised',\n 'child_linked',\n 'attachment_added',\n 'label_added',\n 'label_removed'\n )" + }, + "case_events_actor_type_check": { + "name": "case_events_actor_type_check", + "value": "\"case_events\".\"actor_type\" in ('user', 'agent', 'system')" + } + }, + "isRLSEnabled": false + }, + "public.case_issue_links": { + "name": "case_issue_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_run_id": { + "name": "created_by_run_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": { + "case_issue_links_case_issue_uq": { + "name": "case_issue_links_case_issue_uq", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_issue_links_company_case_idx": { + "name": "case_issue_links_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_issue_links_issue_idx": { + "name": "case_issue_links_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "case_issue_links_company_id_companies_id_fk": { + "name": "case_issue_links_company_id_companies_id_fk", + "tableFrom": "case_issue_links", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_issue_links_case_id_cases_id_fk": { + "name": "case_issue_links_case_id_cases_id_fk", + "tableFrom": "case_issue_links", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_issue_links_issue_id_issues_id_fk": { + "name": "case_issue_links_issue_id_issues_id_fk", + "tableFrom": "case_issue_links", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "case_issue_links_role_check": { + "name": "case_issue_links_role_check", + "value": "\"case_issue_links\".\"role\" in ('origin', 'work', 'reference')" + } + }, + "isRLSEnabled": false + }, + "public.case_labels": { + "name": "case_labels", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label_id": { + "name": "label_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": { + "case_labels_case_label_uq": { + "name": "case_labels_case_label_uq", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "label_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_labels_company_case_idx": { + "name": "case_labels_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_labels_label_idx": { + "name": "case_labels_label_idx", + "columns": [ + { + "expression": "label_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "case_labels_company_id_companies_id_fk": { + "name": "case_labels_company_id_companies_id_fk", + "tableFrom": "case_labels", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_labels_case_id_cases_id_fk": { + "name": "case_labels_case_id_cases_id_fk", + "tableFrom": "case_labels", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_labels_label_id_labels_id_fk": { + "name": "case_labels_label_id_labels_id_fk", + "tableFrom": "case_labels", + "tableTo": "labels", + "columnsFrom": [ + "label_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cases": { + "name": "cases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "case_number": { + "name": "case_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "case_type": { + "name": "case_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "fields": { + "name": "fields", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "parent_case_id": { + "name": "parent_case_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "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": { + "cases_company_case_number_uq": { + "name": "cases_company_case_number_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_identifier_uq": { + "name": "cases_identifier_uq", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_company_type_key_uq": { + "name": "cases_company_type_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_company_status_idx": { + "name": "cases_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_company_type_idx": { + "name": "cases_company_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_company_project_idx": { + "name": "cases_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_parent_idx": { + "name": "cases_parent_idx", + "columns": [ + { + "expression": "parent_case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_title_search_idx": { + "name": "cases_title_search_idx", + "columns": [ + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "cases_identifier_search_idx": { + "name": "cases_identifier_search_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "cases_summary_search_idx": { + "name": "cases_summary_search_idx", + "columns": [ + { + "expression": "summary", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "cases_company_id_companies_id_fk": { + "name": "cases_company_id_companies_id_fk", + "tableFrom": "cases", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cases_project_id_projects_id_fk": { + "name": "cases_project_id_projects_id_fk", + "tableFrom": "cases", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cases_parent_case_id_cases_id_fk": { + "name": "cases_parent_case_id_cases_id_fk", + "tableFrom": "cases", + "tableTo": "cases", + "columnsFrom": [ + "parent_case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cases_created_by_agent_id_agents_id_fk": { + "name": "cases_created_by_agent_id_agents_id_fk", + "tableFrom": "cases", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "cases_status_check": { + "name": "cases_status_check", + "value": "\"cases\".\"status\" in ('draft', 'in_progress', 'in_review', 'approved', 'done', 'cancelled')" + } + }, + "isRLSEnabled": false + }, + "public.chat_actions": { + "name": "chat_actions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "delivery_id": { + "name": "delivery_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "principal_id": { + "name": "principal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_action_id": { + "name": "provider_action_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_actions_provider_action_uq": { + "name": "chat_actions_provider_action_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_action_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_actions_company_id_companies_id_fk": { + "name": "chat_actions_company_id_companies_id_fk", + "tableFrom": "chat_actions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_actions_delivery_id_chat_deliveries_id_fk": { + "name": "chat_actions_delivery_id_chat_deliveries_id_fk", + "tableFrom": "chat_actions", + "tableTo": "chat_deliveries", + "columnsFrom": [ + "delivery_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_actions_company_delivery_fk": { + "name": "chat_actions_company_delivery_fk", + "tableFrom": "chat_actions", + "tableTo": "chat_deliveries", + "columnsFrom": [ + "company_id", + "delivery_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_actions_company_conversation_fk": { + "name": "chat_actions_company_conversation_fk", + "tableFrom": "chat_actions", + "tableTo": "chat_conversations", + "columnsFrom": [ + "company_id", + "conversation_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_actions_company_principal_fk": { + "name": "chat_actions_company_principal_fk", + "tableFrom": "chat_actions", + "tableTo": "chat_external_principals", + "columnsFrom": [ + "company_id", + "principal_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_actions_company_endpoint_fk": { + "name": "chat_actions_company_endpoint_fk", + "tableFrom": "chat_actions", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_agent_routes": { + "name": "chat_agent_routes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_endpoint_id": { + "name": "source_endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "destination_endpoint_id": { + "name": "destination_endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "trigger_mode": { + "name": "trigger_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'explicit_mention'" + }, + "max_hops": { + "name": "max_hops", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_agent_routes_pair_uq": { + "name": "chat_agent_routes_pair_uq", + "columns": [ + { + "expression": "source_endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "destination_endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_agent_routes_company_id_companies_id_fk": { + "name": "chat_agent_routes_company_id_companies_id_fk", + "tableFrom": "chat_agent_routes", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_agent_routes_company_source_fk": { + "name": "chat_agent_routes_company_source_fk", + "tableFrom": "chat_agent_routes", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "source_endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_agent_routes_company_destination_fk": { + "name": "chat_agent_routes_company_destination_fk", + "tableFrom": "chat_agent_routes", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "destination_endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_agent_routes_hops_check": { + "name": "chat_agent_routes_hops_check", + "value": "\"chat_agent_routes\".\"max_hops\" between 1 and 8" + } + }, + "isRLSEnabled": false + }, + "public.chat_conversations": { + "name": "chat_conversations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "external_conversation_id": { + "name": "external_conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_thread_id": { + "name": "external_thread_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "session_generation": { + "name": "session_generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "external_label": { + "name": "external_label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_url": { + "name": "provider_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_direct_message": { + "name": "is_direct_message", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_activity_at": { + "name": "last_activity_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": { + "chat_conversations_issue_idx": { + "name": "chat_conversations_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_conversations_thread_uq": { + "name": "chat_conversations_thread_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_thread_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "session_generation", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_conversations_company_id_companies_id_fk": { + "name": "chat_conversations_company_id_companies_id_fk", + "tableFrom": "chat_conversations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_conversations_resource_id_chat_endpoint_resources_id_fk": { + "name": "chat_conversations_resource_id_chat_endpoint_resources_id_fk", + "tableFrom": "chat_conversations", + "tableTo": "chat_endpoint_resources", + "columnsFrom": [ + "resource_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_conversations_issue_id_issues_id_fk": { + "name": "chat_conversations_issue_id_issues_id_fk", + "tableFrom": "chat_conversations", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "chat_conversations_company_issue_fk": { + "name": "chat_conversations_company_issue_fk", + "tableFrom": "chat_conversations", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_conversations_company_endpoint_fk": { + "name": "chat_conversations_company_endpoint_fk", + "tableFrom": "chat_conversations", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_conversations_company_resource_fk": { + "name": "chat_conversations_company_resource_fk", + "tableFrom": "chat_conversations", + "tableTo": "chat_endpoint_resources", + "columnsFrom": [ + "company_id", + "resource_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_conversations_company_id_uq": { + "name": "chat_conversations_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "chat_conversations_state_check": { + "name": "chat_conversations_state_check", + "value": "\"chat_conversations\".\"state\" in ('active', 'waiting', 'completed', 'unavailable', 'endpoint_removed')" + } + }, + "isRLSEnabled": false + }, + "public.chat_deliveries": { + "name": "chat_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "principal_id": { + "name": "principal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider_event_id": { + "name": "provider_event_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deduplication_key": { + "name": "deduplication_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_kind": { + "name": "event_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "normalized_event": { + "name": "normalized_event", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "redacted_error": { + "name": "redacted_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "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 + }, + "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": { + "chat_deliveries_work_idx": { + "name": "chat_deliveries_work_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_deliveries_event_uq": { + "name": "chat_deliveries_event_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_deliveries_dedupe_uq": { + "name": "chat_deliveries_dedupe_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deduplication_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_deliveries_company_id_companies_id_fk": { + "name": "chat_deliveries_company_id_companies_id_fk", + "tableFrom": "chat_deliveries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_deliveries_conversation_id_chat_conversations_id_fk": { + "name": "chat_deliveries_conversation_id_chat_conversations_id_fk", + "tableFrom": "chat_deliveries", + "tableTo": "chat_conversations", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_deliveries_principal_id_chat_external_principals_id_fk": { + "name": "chat_deliveries_principal_id_chat_external_principals_id_fk", + "tableFrom": "chat_deliveries", + "tableTo": "chat_external_principals", + "columnsFrom": [ + "principal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_deliveries_company_endpoint_fk": { + "name": "chat_deliveries_company_endpoint_fk", + "tableFrom": "chat_deliveries", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_deliveries_company_conversation_fk": { + "name": "chat_deliveries_company_conversation_fk", + "tableFrom": "chat_deliveries", + "tableTo": "chat_conversations", + "columnsFrom": [ + "company_id", + "conversation_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_deliveries_company_principal_fk": { + "name": "chat_deliveries_company_principal_fk", + "tableFrom": "chat_deliveries", + "tableTo": "chat_external_principals", + "columnsFrom": [ + "company_id", + "principal_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_deliveries_company_id_uq": { + "name": "chat_deliveries_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "chat_deliveries_state_check": { + "name": "chat_deliveries_state_check", + "value": "\"chat_deliveries\".\"state\" in ('received', 'filtered', 'processing', 'processed', 'retry', 'failed')" + } + }, + "isRLSEnabled": false + }, + "public.chat_endpoint_leases": { + "name": "chat_endpoint_leases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "lease_key": { + "name": "lease_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "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": { + "chat_endpoint_leases_active_uq": { + "name": "chat_endpoint_leases_active_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lease_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoint_leases_expiry_idx": { + "name": "chat_endpoint_leases_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_endpoint_leases_company_id_companies_id_fk": { + "name": "chat_endpoint_leases_company_id_companies_id_fk", + "tableFrom": "chat_endpoint_leases", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_endpoint_leases_company_endpoint_fk": { + "name": "chat_endpoint_leases_company_endpoint_fk", + "tableFrom": "chat_endpoint_leases", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_endpoint_resources": { + "name": "chat_endpoint_resources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_resource_id": { + "name": "provider_resource_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_provider_resource_id": { + "name": "parent_provider_resource_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "detail": { + "name": "detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_url": { + "name": "provider_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "availability": { + "name": "availability", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'available'" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "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()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_endpoint_resources_endpoint_idx": { + "name": "chat_endpoint_resources_endpoint_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoint_resources_external_uq": { + "name": "chat_endpoint_resources_external_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_endpoint_resources_company_id_companies_id_fk": { + "name": "chat_endpoint_resources_company_id_companies_id_fk", + "tableFrom": "chat_endpoint_resources", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_endpoint_resources_company_endpoint_fk": { + "name": "chat_endpoint_resources_company_endpoint_fk", + "tableFrom": "chat_endpoint_resources", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_endpoint_resources_company_id_uq": { + "name": "chat_endpoint_resources_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "chat_endpoint_resources_availability_check": { + "name": "chat_endpoint_resources_availability_check", + "value": "\"chat_endpoint_resources\".\"availability\" in ('available', 'unavailable', 'removed')" + } + }, + "isRLSEnabled": false + }, + "public.chat_endpoints": { + "name": "chat_endpoints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public_id": { + "name": "public_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "publication_mode": { + "name": "publication_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'automatic'" + }, + "external_execution_policy": { + "name": "external_execution_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'restricted'" + }, + "assigned_agent_id": { + "name": "assigned_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sponsor_user_id": { + "name": "sponsor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "deployment_mode": { + "name": "deployment_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'direct'" + }, + "provider_account_id": { + "name": "provider_account_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_account_label": { + "name": "provider_account_label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bot_external_id": { + "name": "bot_external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bot_username": { + "name": "bot_username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bot_display_name": { + "name": "bot_display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bot_avatar_url": { + "name": "bot_avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "allow_direct_messages": { + "name": "allow_direct_messages", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "allow_group_chats": { + "name": "allow_group_chats", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "allow_unlinked_people": { + "name": "allow_unlinked_people", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "concurrency_policy": { + "name": "concurrency_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queue'" + }, + "capabilities": { + "name": "capabilities", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"threads\":false,\"directMessages\":false,\"nativeStreaming\":false,\"messageEdits\":false,\"messageDeletes\":false,\"reactions\":false,\"files\":false,\"cards\":false,\"actions\":false,\"modals\":false,\"slashCommands\":false,\"ephemeralMessages\":false,\"proactiveDirectMessages\":false}'::jsonb" + }, + "setup": { + "name": "setup", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"step\":\"provider_setup\"}'::jsonb" + }, + "health_message": { + "name": "health_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_event_at": { + "name": "last_event_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_publication_at": { + "name": "last_publication_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "activated_at": { + "name": "activated_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": { + "chat_endpoints_company_idx": { + "name": "chat_endpoints_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_agent_idx": { + "name": "chat_endpoints_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assigned_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_status_idx": { + "name": "chat_endpoints_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_public_id_uq": { + "name": "chat_endpoints_public_id_uq", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_agentmail_inbox_uq": { + "name": "chat_endpoints_agentmail_inbox_uq", + "columns": [ + { + "expression": "bot_external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat_endpoints\".\"provider\" = 'agentmail' and \"chat_endpoints\".\"status\" != 'archived' and \"chat_endpoints\".\"bot_external_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_connection_uq": { + "name": "chat_endpoints_connection_uq", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_live_bot_external_uq": { + "name": "chat_endpoints_live_bot_external_uq", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bot_external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat_endpoints\".\"status\" in ('verifying', 'active', 'paused', 'attention')\n and \"chat_endpoints\".\"provider_account_id\" is not null\n and \"chat_endpoints\".\"bot_external_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_photon_number_uq": { + "name": "chat_endpoints_photon_number_uq", + "columns": [ + { + "expression": "bot_external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat_endpoints\".\"provider\" = 'imessage-photon' and \"chat_endpoints\".\"status\" <> 'archived' and \"chat_endpoints\".\"bot_external_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_live_discord_bot_external_uq": { + "name": "chat_endpoints_live_discord_bot_external_uq", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bot_external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat_endpoints\".\"provider\" = 'discord'\n and \"chat_endpoints\".\"status\" in ('verifying', 'active', 'paused', 'attention')\n and \"chat_endpoints\".\"bot_external_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_live_global_app_bot_external_uq": { + "name": "chat_endpoints_live_global_app_bot_external_uq", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bot_external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat_endpoints\".\"provider\" in ('github', 'microsoft-teams')\n and \"chat_endpoints\".\"status\" in ('verifying', 'active', 'paused', 'attention')\n and \"chat_endpoints\".\"bot_external_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_live_bot_username_uq": { + "name": "chat_endpoints_live_bot_username_uq", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bot_username", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat_endpoints\".\"status\" in ('verifying', 'active', 'paused', 'attention')\n and \"chat_endpoints\".\"provider_account_id\" is not null\n and \"chat_endpoints\".\"bot_username\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_endpoints_company_id_companies_id_fk": { + "name": "chat_endpoints_company_id_companies_id_fk", + "tableFrom": "chat_endpoints", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_endpoints_assigned_agent_id_agents_id_fk": { + "name": "chat_endpoints_assigned_agent_id_agents_id_fk", + "tableFrom": "chat_endpoints", + "tableTo": "agents", + "columnsFrom": [ + "assigned_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "chat_endpoints_company_agent_fk": { + "name": "chat_endpoints_company_agent_fk", + "tableFrom": "chat_endpoints", + "tableTo": "agents", + "columnsFrom": [ + "company_id", + "assigned_agent_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_endpoints_company_connection_fk": { + "name": "chat_endpoints_company_connection_fk", + "tableFrom": "chat_endpoints", + "tableTo": "tool_connections", + "columnsFrom": [ + "company_id", + "connection_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_endpoints_company_id_uq": { + "name": "chat_endpoints_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "chat_endpoints_publication_mode_check": { + "name": "chat_endpoints_publication_mode_check", + "value": "\"chat_endpoints\".\"publication_mode\" in ('automatic', 'explicit')" + }, + "chat_endpoints_execution_policy_check": { + "name": "chat_endpoints_execution_policy_check", + "value": "\"chat_endpoints\".\"external_execution_policy\" in ('restricted', 'agent')" + }, + "chat_endpoints_email_policy_check": { + "name": "chat_endpoints_email_policy_check", + "value": "\"chat_endpoints\".\"provider\" <> 'agentmail' or (\"chat_endpoints\".\"publication_mode\" = 'explicit' and \"chat_endpoints\".\"external_execution_policy\" = 'agent')" + }, + "chat_endpoints_provider_check": { + "name": "chat_endpoints_provider_check", + "value": "\"chat_endpoints\".\"provider\" in ('slack', 'github', 'discord', 'microsoft-teams', 'telegram', 'agentmail', 'imessage-photon')" + }, + "chat_endpoints_status_check": { + "name": "chat_endpoints_status_check", + "value": "\"chat_endpoints\".\"status\" in ('draft', 'verifying', 'active', 'paused', 'attention', 'revoked', 'archived')" + }, + "chat_endpoints_deployment_check": { + "name": "chat_endpoints_deployment_check", + "value": "\"chat_endpoints\".\"deployment_mode\" in ('direct', 'relay')" + }, + "chat_endpoints_concurrency_check": { + "name": "chat_endpoints_concurrency_check", + "value": "\"chat_endpoints\".\"concurrency_policy\" in ('burst', 'queue', 'debounce', 'drop', 'concurrent')" + } + }, + "isRLSEnabled": false + }, + "public.chat_external_principals": { + "name": "chat_external_principals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_account_id": { + "name": "provider_account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'user'" + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "handle": { + "name": "handle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_bot": { + "name": "is_bot", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_external_principals_company_idx": { + "name": "chat_external_principals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_external_principals_external_uq": { + "name": "chat_external_principals_external_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_external_principals_company_id_companies_id_fk": { + "name": "chat_external_principals_company_id_companies_id_fk", + "tableFrom": "chat_external_principals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_external_principals_company_id_uq": { + "name": "chat_external_principals_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "chat_external_principals_provider_check": { + "name": "chat_external_principals_provider_check", + "value": "\"chat_external_principals\".\"provider\" in ('slack', 'github', 'discord', 'microsoft-teams', 'telegram', 'agentmail', 'imessage-photon')" + }, + "chat_external_principals_kind_check": { + "name": "chat_external_principals_kind_check", + "value": "\"chat_external_principals\".\"kind\" in ('user', 'bot', 'app', 'system')" + } + }, + "isRLSEnabled": false + }, + "public.chat_identity_links": { + "name": "chat_identity_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "paperclip_user_id": { + "name": "paperclip_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "confirmation_token_hash": { + "name": "confirmation_token_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "confirmed_at": { + "name": "confirmed_at", + "type": "timestamp with time zone", + "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": { + "chat_identity_links_user_idx": { + "name": "chat_identity_links_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "paperclip_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_identity_links_endpoint_principal_uq": { + "name": "chat_identity_links_endpoint_principal_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_identity_links_company_id_companies_id_fk": { + "name": "chat_identity_links_company_id_companies_id_fk", + "tableFrom": "chat_identity_links", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_identity_links_company_endpoint_fk": { + "name": "chat_identity_links_company_endpoint_fk", + "tableFrom": "chat_identity_links", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_identity_links_company_principal_fk": { + "name": "chat_identity_links_company_principal_fk", + "tableFrom": "chat_identity_links", + "tableTo": "chat_external_principals", + "columnsFrom": [ + "company_id", + "principal_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_identity_links_status_check": { + "name": "chat_identity_links_status_check", + "value": "\"chat_identity_links\".\"status\" in ('pending', 'linked', 'revoked', 'expired')" + } + }, + "isRLSEnabled": false + }, + "public.chat_message_links": { + "name": "chat_message_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "delivery_id": { + "name": "delivery_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "publication_id": { + "name": "publication_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "comment_id": { + "name": "comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider_message_id": { + "name": "provider_message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_message_links_provider_message_uq": { + "name": "chat_message_links_provider_message_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_message_links_company_id_companies_id_fk": { + "name": "chat_message_links_company_id_companies_id_fk", + "tableFrom": "chat_message_links", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_message_links_delivery_id_chat_deliveries_id_fk": { + "name": "chat_message_links_delivery_id_chat_deliveries_id_fk", + "tableFrom": "chat_message_links", + "tableTo": "chat_deliveries", + "columnsFrom": [ + "delivery_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_message_links_publication_id_chat_publications_id_fk": { + "name": "chat_message_links_publication_id_chat_publications_id_fk", + "tableFrom": "chat_message_links", + "tableTo": "chat_publications", + "columnsFrom": [ + "publication_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_message_links_comment_id_issue_comments_id_fk": { + "name": "chat_message_links_comment_id_issue_comments_id_fk", + "tableFrom": "chat_message_links", + "tableTo": "issue_comments", + "columnsFrom": [ + "comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_message_links_company_endpoint_fk": { + "name": "chat_message_links_company_endpoint_fk", + "tableFrom": "chat_message_links", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_message_links_company_delivery_fk": { + "name": "chat_message_links_company_delivery_fk", + "tableFrom": "chat_message_links", + "tableTo": "chat_deliveries", + "columnsFrom": [ + "company_id", + "delivery_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_message_links_company_publication_fk": { + "name": "chat_message_links_company_publication_fk", + "tableFrom": "chat_message_links", + "tableTo": "chat_publications", + "columnsFrom": [ + "company_id", + "publication_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_message_links_company_comment_fk": { + "name": "chat_message_links_company_comment_fk", + "tableFrom": "chat_message_links", + "tableTo": "issue_comments", + "columnsFrom": [ + "company_id", + "comment_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_message_links_company_conversation_fk": { + "name": "chat_message_links_company_conversation_fk", + "tableFrom": "chat_message_links", + "tableTo": "chat_conversations", + "columnsFrom": [ + "company_id", + "conversation_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_message_links_direction_check": { + "name": "chat_message_links_direction_check", + "value": "\"chat_message_links\".\"direction\" in ('inbound', 'outbound')" + } + }, + "isRLSEnabled": false + }, + "public.chat_publications": { + "name": "chat_publications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "comment_id": { + "name": "comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "provider_message_id": { + "name": "provider_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_url": { + "name": "provider_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "redacted_error": { + "name": "redacted_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "published_at": { + "name": "published_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": { + "chat_publications_company_id_uq": { + "name": "chat_publications_company_id_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_publications_work_idx": { + "name": "chat_publications_work_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_publications_idempotency_uq": { + "name": "chat_publications_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_publications_company_id_companies_id_fk": { + "name": "chat_publications_company_id_companies_id_fk", + "tableFrom": "chat_publications", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_publications_issue_id_issues_id_fk": { + "name": "chat_publications_issue_id_issues_id_fk", + "tableFrom": "chat_publications", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "chat_publications_comment_id_issue_comments_id_fk": { + "name": "chat_publications_comment_id_issue_comments_id_fk", + "tableFrom": "chat_publications", + "tableTo": "issue_comments", + "columnsFrom": [ + "comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_publications_company_issue_fk": { + "name": "chat_publications_company_issue_fk", + "tableFrom": "chat_publications", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_publications_company_comment_fk": { + "name": "chat_publications_company_comment_fk", + "tableFrom": "chat_publications", + "tableTo": "issue_comments", + "columnsFrom": [ + "company_id", + "comment_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_publications_company_endpoint_fk": { + "name": "chat_publications_company_endpoint_fk", + "tableFrom": "chat_publications", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_publications_company_conversation_fk": { + "name": "chat_publications_company_conversation_fk", + "tableFrom": "chat_publications", + "tableTo": "chat_conversations", + "columnsFrom": [ + "company_id", + "conversation_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_publications_state_check": { + "name": "chat_publications_state_check", + "value": "\"chat_publications\".\"state\" in ('pending', 'streaming', 'published', 'retry', 'delivery_unknown', 'failed', 'cancelled', 'awaiting_consent')" + } + }, + "isRLSEnabled": false + }, + "public.chat_sdk_state": { + "name": "chat_sdk_state", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "state_key": { + "name": "state_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_sdk_state_key_uq": { + "name": "chat_sdk_state_key_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "state_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_sdk_state_expiry_idx": { + "name": "chat_sdk_state_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_sdk_state_company_id_companies_id_fk": { + "name": "chat_sdk_state_company_id_companies_id_fk", + "tableFrom": "chat_sdk_state", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_sdk_state_company_endpoint_fk": { + "name": "chat_sdk_state_company_endpoint_fk", + "tableFrom": "chat_sdk_state", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_discord_command_owners": { + "name": "chat_discord_command_owners", + "schema": "", + "columns": { + "application_id": { + "name": "application_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "action_id": { + "name": "action_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_discord_command_owners_application_check": { + "name": "chat_discord_command_owners_application_check", + "value": "\"chat_discord_command_owners\".\"application_id\" ~ '^[1-9][0-9]{16,19}$'" + } + }, + "isRLSEnabled": false + }, + "public.chat_teams_file_transfers": { + "name": "chat_teams_file_transfers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "publication_id": { + "name": "publication_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "comment_id": { + "name": "comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "attachment_id": { + "name": "attachment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "authorized_user_id": { + "name": "authorized_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "runtime_generation": { + "name": "runtime_generation", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "credential_fingerprint": { + "name": "credential_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "conversation_generation": { + "name": "conversation_generation", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "source_digest": { + "name": "source_digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "authority_digest": { + "name": "authority_digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "bot_app_id": { + "name": "bot_app_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "aad_object_id": { + "name": "aad_object_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider_conversation_id": { + "name": "provider_conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_user_id": { + "name": "provider_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sha256": { + "name": "sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "byte_size": { + "name": "byte_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "filename": { + "name": "filename", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_sha256": { + "name": "token_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'consent_pending'" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "attempt_id": { + "name": "attempt_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "attempt_expires_at": { + "name": "attempt_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "consent_message_id": { + "name": "consent_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "file_info_message_id": { + "name": "file_info_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "response_activity_id": { + "name": "response_activity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "response_digest": { + "name": "response_digest", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "private_state": { + "name": "private_state", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_teams_file_transfers_publication_uq": { + "name": "chat_teams_file_transfers_publication_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "publication_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_teams_file_transfers_token_uq": { + "name": "chat_teams_file_transfers_token_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "token_sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_teams_file_transfers_work_idx": { + "name": "chat_teams_file_transfers_work_idx", + "columns": [ + { + "expression": "phase", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "attempt_expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_teams_file_transfers_company_id_companies_id_fk": { + "name": "chat_teams_file_transfers_company_id_companies_id_fk", + "tableFrom": "chat_teams_file_transfers", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_teams_file_transfers_issue_id_issues_id_fk": { + "name": "chat_teams_file_transfers_issue_id_issues_id_fk", + "tableFrom": "chat_teams_file_transfers", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "chat_teams_file_transfers_company_id_endpoint_id_chat_endpoints_company_id_id_fk": { + "name": "chat_teams_file_transfers_company_id_endpoint_id_chat_endpoints_company_id_id_fk", + "tableFrom": "chat_teams_file_transfers", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_teams_file_transfers_company_id_publication_id_chat_publications_company_id_id_fk": { + "name": "chat_teams_file_transfers_company_id_publication_id_chat_publications_company_id_id_fk", + "tableFrom": "chat_teams_file_transfers", + "tableTo": "chat_publications", + "columnsFrom": [ + "company_id", + "publication_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_teams_file_transfers_company_id_conversation_id_chat_conversations_company_id_id_fk": { + "name": "chat_teams_file_transfers_company_id_conversation_id_chat_conversations_company_id_id_fk", + "tableFrom": "chat_teams_file_transfers", + "tableTo": "chat_conversations", + "columnsFrom": [ + "company_id", + "conversation_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_teams_file_transfers_company_id_principal_id_chat_external_principals_company_id_id_fk": { + "name": "chat_teams_file_transfers_company_id_principal_id_chat_external_principals_company_id_id_fk", + "tableFrom": "chat_teams_file_transfers", + "tableTo": "chat_external_principals", + "columnsFrom": [ + "company_id", + "principal_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_teams_file_transfers_phase_check": { + "name": "chat_teams_file_transfers_phase_check", + "value": "\"chat_teams_file_transfers\".\"phase\" in ('consent_pending','consent_sending','consent_unknown','awaiting_consent','upload_pending','uploading','upload_unknown','file_info_pending','file_info_sending','file_info_unknown','delivered','declined','expired','cancelled','conflict')" + }, + "chat_teams_file_transfers_bounds_check": { + "name": "chat_teams_file_transfers_bounds_check", + "value": "\"chat_teams_file_transfers\".\"version\" > 0 and \"chat_teams_file_transfers\".\"runtime_generation\" >= 0 and \"chat_teams_file_transfers\".\"conversation_generation\" > 0 and \"chat_teams_file_transfers\".\"byte_size\" > 0 and \"chat_teams_file_transfers\".\"byte_size\" < 62914560" + }, + "chat_teams_file_transfers_hash_check": { + "name": "chat_teams_file_transfers_hash_check", + "value": "\"chat_teams_file_transfers\".\"source_digest\" ~ '^[a-f0-9]{64}$' and \"chat_teams_file_transfers\".\"authority_digest\" ~ '^[a-f0-9]{64}$' and \"chat_teams_file_transfers\".\"sha256\" ~ '^[a-f0-9]{64}$' and \"chat_teams_file_transfers\".\"token_sha256\" ~ '^[a-f0-9]{64}$'" + }, + "chat_teams_file_transfers_attempt_check": { + "name": "chat_teams_file_transfers_attempt_check", + "value": "(\"chat_teams_file_transfers\".\"attempt_id\" is null) = (\"chat_teams_file_transfers\".\"attempt_expires_at\" is null)" + } + }, + "isRLSEnabled": false + }, + "public.cli_auth_challenges": { + "name": "cli_auth_challenges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "secret_hash": { + "name": "secret_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_access": { + "name": "requested_access", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'board'" + }, + "requested_company_id": { + "name": "requested_company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "pending_key_hash": { + "name": "pending_key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pending_key_name": { + "name": "pending_key_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "approved_by_user_id": { + "name": "approved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "board_api_key_id": { + "name": "board_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approved_at": { + "name": "approved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cli_auth_challenges_secret_hash_idx": { + "name": "cli_auth_challenges_secret_hash_idx", + "columns": [ + { + "expression": "secret_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cli_auth_challenges_approved_by_idx": { + "name": "cli_auth_challenges_approved_by_idx", + "columns": [ + { + "expression": "approved_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cli_auth_challenges_requested_company_idx": { + "name": "cli_auth_challenges_requested_company_idx", + "columns": [ + { + "expression": "requested_company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cli_auth_challenges_requested_company_id_companies_id_fk": { + "name": "cli_auth_challenges_requested_company_id_companies_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "companies", + "columnsFrom": [ + "requested_company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cli_auth_challenges_approved_by_user_id_user_id_fk": { + "name": "cli_auth_challenges_approved_by_user_id_user_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "user", + "columnsFrom": [ + "approved_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cli_auth_challenges_board_api_key_id_board_api_keys_id_fk": { + "name": "cli_auth_challenges_board_api_key_id_board_api_keys_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "board_api_keys", + "columnsFrom": [ + "board_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.companies": { + "name": "companies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "issue_prefix": { + "name": "issue_prefix", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'PAP'" + }, + "issue_counter": { + "name": "issue_counter", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "budget_monthly_cents": { + "name": "budget_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "spent_monthly_cents": { + "name": "spent_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "default_responsible_user_id": { + "name": "default_responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "require_board_approval_for_new_agents": { + "name": "require_board_approval_for_new_agents", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "interaction_resolver_governance": { + "name": "interaction_resolver_governance", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "feedback_data_sharing_enabled": { + "name": "feedback_data_sharing_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "feedback_data_sharing_consent_at": { + "name": "feedback_data_sharing_consent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "feedback_data_sharing_consent_by_user_id": { + "name": "feedback_data_sharing_consent_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feedback_data_sharing_terms_version": { + "name": "feedback_data_sharing_terms_version", + "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": { + "companies_issue_prefix_idx": { + "name": "companies_issue_prefix_idx", + "columns": [ + { + "expression": "issue_prefix", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_logos": { + "name": "company_logos", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_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": { + "company_logos_company_uq": { + "name": "company_logos_company_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_logos_asset_uq": { + "name": "company_logos_asset_uq", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_logos_company_id_companies_id_fk": { + "name": "company_logos_company_id_companies_id_fk", + "tableFrom": "company_logos", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_logos_asset_id_assets_id_fk": { + "name": "company_logos_asset_id_assets_id_fk", + "tableFrom": "company_logos", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_memberships": { + "name": "company_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "principal_type": { + "name": "principal_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "membership_role": { + "name": "membership_role", + "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": { + "company_memberships_company_principal_unique_idx": { + "name": "company_memberships_company_principal_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_memberships_principal_status_idx": { + "name": "company_memberships_principal_status_idx", + "columns": [ + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_memberships_company_status_idx": { + "name": "company_memberships_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_memberships_company_id_companies_id_fk": { + "name": "company_memberships_company_id_companies_id_fk", + "tableFrom": "company_memberships", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_onboarding_seeds": { + "name": "company_onboarding_seeds", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mission": { + "name": "mission", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_name": { + "name": "agent_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_role": { + "name": "agent_role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "first_task_title": { + "name": "first_task_title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "first_task_details": { + "name": "first_task_details", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "applied_at": { + "name": "applied_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_onboarding_seeds_company_uq": { + "name": "company_onboarding_seeds_company_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_onboarding_seeds_company_id_companies_id_fk": { + "name": "company_onboarding_seeds_company_id_companies_id_fk", + "tableFrom": "company_onboarding_seeds", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_onboarding_seeds_goal_id_goals_id_fk": { + "name": "company_onboarding_seeds_goal_id_goals_id_fk", + "tableFrom": "company_onboarding_seeds", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_onboarding_seeds_agent_id_agents_id_fk": { + "name": "company_onboarding_seeds_agent_id_agents_id_fk", + "tableFrom": "company_onboarding_seeds", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_onboarding_seeds_issue_id_issues_id_fk": { + "name": "company_onboarding_seeds_issue_id_issues_id_fk", + "tableFrom": "company_onboarding_seeds", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secret_bindings": { + "name": "company_secret_bindings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "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 + }, + "config_path": { + "name": "config_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version_selector": { + "name": "version_selector", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'latest'" + }, + "required": { + "name": "required", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "projection_class": { + "name": "projection_class", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unclassified'" + }, + "projection_allowlist_key": { + "name": "projection_allowlist_key", + "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": { + "company_secret_bindings_company_idx": { + "name": "company_secret_bindings_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_bindings_secret_idx": { + "name": "company_secret_bindings_secret_idx", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_bindings_target_idx": { + "name": "company_secret_bindings_target_idx", + "columns": [ + { + "expression": "company_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": {} + }, + "company_secret_bindings_target_path_uq": { + "name": "company_secret_bindings_target_path_uq", + "columns": [ + { + "expression": "company_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" + }, + { + "expression": "config_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secret_bindings_company_id_companies_id_fk": { + "name": "company_secret_bindings_company_id_companies_id_fk", + "tableFrom": "company_secret_bindings", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "company_secret_bindings_secret_id_company_secrets_id_fk": { + "name": "company_secret_bindings_secret_id_company_secrets_id_fk", + "tableFrom": "company_secret_bindings", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secret_proposals": { + "name": "company_secret_proposals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "proposed_name": { + "name": "proposed_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "proposed_key": { + "name": "proposed_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "proposed_description": { + "name": "proposed_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "justification": { + "name": "justification", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value_ciphertext": { + "name": "value_ciphertext", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "value_fingerprint_sha256": { + "name": "value_fingerprint_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "value_length": { + "name": "value_length", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "secret_proposal_id": { + "name": "secret_proposal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_id": { + "name": "target_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "config_path": { + "name": "config_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "projection_class": { + "name": "projection_class", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unclassified'" + }, + "binding_target_policy_snapshot": { + "name": "binding_target_policy_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "proposer_ancestor_ids_snapshot": { + "name": "proposer_ancestor_ids_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "target_ancestor_ids_snapshot": { + "name": "target_ancestor_ids_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "proposed_by_agent_id": { + "name": "proposed_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "origin_issue_id": { + "name": "origin_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "origin_run_id": { + "name": "origin_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "interaction_id": { + "name": "interaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_by_user_id": { + "name": "resolved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "resolution_reason": { + "name": "resolution_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_secret_id": { + "name": "created_secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "applied_binding_config_path": { + "name": "applied_binding_config_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ciphertext_scrubbed_at": { + "name": "ciphertext_scrubbed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_secret_proposals_company_status_idx": { + "name": "company_secret_proposals_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_proposals_proposer_status_idx": { + "name": "company_secret_proposals_proposer_status_idx", + "columns": [ + { + "expression": "proposed_by_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_proposals_expiry_idx": { + "name": "company_secret_proposals_expiry_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_proposals_secret_proposal_idx": { + "name": "company_secret_proposals_secret_proposal_idx", + "columns": [ + { + "expression": "secret_proposal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_proposals_interaction_idx": { + "name": "company_secret_proposals_interaction_idx", + "columns": [ + { + "expression": "interaction_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secret_proposals_company_id_companies_id_fk": { + "name": "company_secret_proposals_company_id_companies_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "company_secret_proposals_secret_id_company_secrets_id_fk": { + "name": "company_secret_proposals_secret_id_company_secrets_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_secret_proposals_secret_proposal_id_company_secret_proposals_id_fk": { + "name": "company_secret_proposals_secret_proposal_id_company_secret_proposals_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "company_secret_proposals", + "columnsFrom": [ + "secret_proposal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_proposals_target_id_agents_id_fk": { + "name": "company_secret_proposals_target_id_agents_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "agents", + "columnsFrom": [ + "target_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_proposals_proposed_by_agent_id_agents_id_fk": { + "name": "company_secret_proposals_proposed_by_agent_id_agents_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "agents", + "columnsFrom": [ + "proposed_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_proposals_origin_issue_id_issues_id_fk": { + "name": "company_secret_proposals_origin_issue_id_issues_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "issues", + "columnsFrom": [ + "origin_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_secret_proposals_origin_run_id_heartbeat_runs_id_fk": { + "name": "company_secret_proposals_origin_run_id_heartbeat_runs_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "origin_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_proposals_interaction_id_issue_thread_interactions_id_fk": { + "name": "company_secret_proposals_interaction_id_issue_thread_interactions_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "issue_thread_interactions", + "columnsFrom": [ + "interaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_secret_proposals_created_secret_id_company_secrets_id_fk": { + "name": "company_secret_proposals_created_secret_id_company_secrets_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "company_secrets", + "columnsFrom": [ + "created_secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "company_secret_proposals_kind_check": { + "name": "company_secret_proposals_kind_check", + "value": "\"company_secret_proposals\".\"kind\" in ('secret', 'binding')" + }, + "company_secret_proposals_status_check": { + "name": "company_secret_proposals_status_check", + "value": "\"company_secret_proposals\".\"status\" in ('pending', 'approved', 'rejected', 'withdrawn', 'expired')" + }, + "company_secret_proposals_projection_check": { + "name": "company_secret_proposals_projection_check", + "value": "\"company_secret_proposals\".\"projection_class\" = 'unclassified'" + }, + "company_secret_proposals_shape_check": { + "name": "company_secret_proposals_shape_check", + "value": "(\n \"company_secret_proposals\".\"kind\" = 'secret'\n and \"company_secret_proposals\".\"proposed_name\" is not null\n and \"company_secret_proposals\".\"proposed_key\" is not null\n and \"company_secret_proposals\".\"secret_id\" is null\n and \"company_secret_proposals\".\"secret_proposal_id\" is null\n and \"company_secret_proposals\".\"target_type\" is null\n and \"company_secret_proposals\".\"target_id\" is null\n and \"company_secret_proposals\".\"config_path\" is null\n ) or (\n \"company_secret_proposals\".\"kind\" = 'binding'\n and ((\"company_secret_proposals\".\"secret_id\" is not null)::int + (\"company_secret_proposals\".\"secret_proposal_id\" is not null)::int) = 1\n and \"company_secret_proposals\".\"target_type\" = 'agent'\n and \"company_secret_proposals\".\"target_id\" is not null\n and \"company_secret_proposals\".\"config_path\" is not null\n )" + } + }, + "isRLSEnabled": false + }, + "public.company_secret_provider_configs": { + "name": "company_secret_provider_configs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "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, + "default": "'ready'" + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "health_checked_at": { + "name": "health_checked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "health_message": { + "name": "health_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "health_details": { + "name": "health_details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "disabled_at": { + "name": "disabled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_secret_provider_configs_company_idx": { + "name": "company_secret_provider_configs_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_provider_configs_company_provider_idx": { + "name": "company_secret_provider_configs_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_provider_configs_default_uq": { + "name": "company_secret_provider_configs_default_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"company_secret_provider_configs\".\"is_default\" = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secret_provider_configs_company_id_companies_id_fk": { + "name": "company_secret_provider_configs_company_id_companies_id_fk", + "tableFrom": "company_secret_provider_configs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_provider_configs_created_by_agent_id_agents_id_fk": { + "name": "company_secret_provider_configs_created_by_agent_id_agents_id_fk", + "tableFrom": "company_secret_provider_configs", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secret_versions": { + "name": "company_secret_versions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "material": { + "name": "material", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "value_sha256": { + "name": "value_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_version_ref": { + "name": "provider_version_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'current'" + }, + "fingerprint_sha256": { + "name": "fingerprint_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rotation_job_id": { + "name": "rotation_job_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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": { + "company_secret_versions_secret_idx": { + "name": "company_secret_versions_secret_idx", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_versions_value_sha256_idx": { + "name": "company_secret_versions_value_sha256_idx", + "columns": [ + { + "expression": "value_sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_versions_fingerprint_idx": { + "name": "company_secret_versions_fingerprint_idx", + "columns": [ + { + "expression": "fingerprint_sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_versions_secret_version_uq": { + "name": "company_secret_versions_secret_version_uq", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secret_versions_secret_id_company_secrets_id_fk": { + "name": "company_secret_versions_secret_id_company_secrets_id_fk", + "tableFrom": "company_secret_versions", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_versions_created_by_agent_id_agents_id_fk": { + "name": "company_secret_versions_created_by_agent_id_agents_id_fk", + "tableFrom": "company_secret_versions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secrets": { + "name": "company_secrets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company'" + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_secret_definition_id": { + "name": "user_secret_definition_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_encrypted'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "managed_mode": { + "name": "managed_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip_managed'" + }, + "external_ref": { + "name": "external_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_config_id": { + "name": "provider_config_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider_metadata": { + "name": "provider_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "latest_version": { + "name": "latest_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_resolved_at": { + "name": "last_resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_rotated_at": { + "name": "last_rotated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_secrets_company_idx": { + "name": "company_secrets_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_scope_idx": { + "name": "company_secrets_company_scope_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_owner_idx": { + "name": "company_secrets_company_owner_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_user_definition_owner_idx": { + "name": "company_secrets_user_definition_owner_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_secret_definition_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_provider_idx": { + "name": "company_secrets_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_provider_config_idx": { + "name": "company_secrets_provider_config_idx", + "columns": [ + { + "expression": "provider_config_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_name_uq": { + "name": "company_secrets_company_name_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"company_secrets\".\"scope\" = 'company' and \"company_secrets\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_key_uq": { + "name": "company_secrets_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"company_secrets\".\"scope\" = 'company' and \"company_secrets\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_user_definition_owner_uq": { + "name": "company_secrets_user_definition_owner_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_secret_definition_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"company_secrets\".\"scope\" = 'user' and \"company_secrets\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secrets_company_id_companies_id_fk": { + "name": "company_secrets_company_id_companies_id_fk", + "tableFrom": "company_secrets", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "company_secrets_user_secret_definition_id_user_secret_definitions_id_fk": { + "name": "company_secrets_user_secret_definition_id_user_secret_definitions_id_fk", + "tableFrom": "company_secrets", + "tableTo": "user_secret_definitions", + "columnsFrom": [ + "user_secret_definition_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_secrets_provider_config_id_company_secret_provider_configs_id_fk": { + "name": "company_secrets_provider_config_id_company_secret_provider_configs_id_fk", + "tableFrom": "company_secrets", + "tableTo": "company_secret_provider_configs", + "columnsFrom": [ + "provider_config_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_secrets_created_by_agent_id_agents_id_fk": { + "name": "company_secrets_created_by_agent_id_agents_id_fk", + "tableFrom": "company_secrets", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "company_secrets_scope_shape_check": { + "name": "company_secrets_scope_shape_check", + "value": "(\n \"company_secrets\".\"scope\" = 'company'\n and \"company_secrets\".\"owner_user_id\" is null\n and \"company_secrets\".\"user_secret_definition_id\" is null\n ) or (\n \"company_secrets\".\"scope\" = 'user'\n and \"company_secrets\".\"owner_user_id\" is not null\n and \"company_secrets\".\"user_secret_definition_id\" is not null\n )" + } + }, + "isRLSEnabled": false + }, + "public.company_skill_policies": { + "name": "company_skill_policies", + "schema": "", + "columns": { + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "schema_version": { + "name": "schema_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "default_effect": { + "name": "default_effect", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rules": { + "name": "rules", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "company_skill_policies_company_id_companies_id_fk": { + "name": "company_skill_policies_company_id_companies_id_fk", + "tableFrom": "company_skill_policies", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_comments": { + "name": "company_skill_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_skill_id": { + "name": "company_skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "parent_comment_id": { + "name": "parent_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_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": { + "company_skill_comments_company_skill_created_idx": { + "name": "company_skill_comments_company_skill_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_comments_parent_idx": { + "name": "company_skill_comments_parent_idx", + "columns": [ + { + "expression": "parent_comment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_comments_company_id_companies_id_fk": { + "name": "company_skill_comments_company_id_companies_id_fk", + "tableFrom": "company_skill_comments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_comments_company_skill_id_company_skills_id_fk": { + "name": "company_skill_comments_company_skill_id_company_skills_id_fk", + "tableFrom": "company_skill_comments", + "tableTo": "company_skills", + "columnsFrom": [ + "company_skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_comments_parent_comment_id_company_skill_comments_id_fk": { + "name": "company_skill_comments_parent_comment_id_company_skill_comments_id_fk", + "tableFrom": "company_skill_comments", + "tableTo": "company_skill_comments", + "columnsFrom": [ + "parent_comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_skill_comments_author_agent_id_agents_id_fk": { + "name": "company_skill_comments_author_agent_id_agents_id_fk", + "tableFrom": "company_skill_comments", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_stars": { + "name": "company_skill_stars", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_skill_id": { + "name": "company_skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skill_stars_skill_agent_idx": { + "name": "company_skill_stars_skill_agent_idx", + "columns": [ + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_stars_skill_user_idx": { + "name": "company_skill_stars_skill_user_idx", + "columns": [ + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_stars_company_skill_created_idx": { + "name": "company_skill_stars_company_skill_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_stars_company_id_companies_id_fk": { + "name": "company_skill_stars_company_id_companies_id_fk", + "tableFrom": "company_skill_stars", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_stars_company_skill_id_company_skills_id_fk": { + "name": "company_skill_stars_company_skill_id_company_skills_id_fk", + "tableFrom": "company_skill_stars", + "tableTo": "company_skills", + "columnsFrom": [ + "company_skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_stars_agent_id_agents_id_fk": { + "name": "company_skill_stars_agent_id_agents_id_fk", + "tableFrom": "company_skill_stars", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_test_inputs": { + "name": "company_skill_test_inputs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "skill_id": { + "name": "skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_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": { + "company_skill_test_inputs_company_skill_name_idx": { + "name": "company_skill_test_inputs_company_skill_name_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_test_inputs_company_skill_active_idx": { + "name": "company_skill_test_inputs_company_skill_active_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_test_inputs_company_id_companies_id_fk": { + "name": "company_skill_test_inputs_company_id_companies_id_fk", + "tableFrom": "company_skill_test_inputs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_test_inputs_skill_id_company_skills_id_fk": { + "name": "company_skill_test_inputs_skill_id_company_skills_id_fk", + "tableFrom": "company_skill_test_inputs", + "tableTo": "company_skills", + "columnsFrom": [ + "skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_test_run_templates": { + "name": "company_skill_test_run_templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_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": { + "company_skill_test_run_templates_company_active_idx": { + "name": "company_skill_test_run_templates_company_active_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_test_run_templates_company_id_companies_id_fk": { + "name": "company_skill_test_run_templates_company_id_companies_id_fk", + "tableFrom": "company_skill_test_run_templates", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_test_run_templates_created_by_agent_id_agents_id_fk": { + "name": "company_skill_test_run_templates_created_by_agent_id_agents_id_fk", + "tableFrom": "company_skill_test_run_templates", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_skill_test_run_templates_updated_by_agent_id_agents_id_fk": { + "name": "company_skill_test_run_templates_updated_by_agent_id_agents_id_fk", + "tableFrom": "company_skill_test_run_templates", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_test_runs": { + "name": "company_skill_test_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "skill_id": { + "name": "skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "input_id": { + "name": "input_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "input_snapshot": { + "name": "input_snapshot", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "skill_version_id": { + "name": "skill_version_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_config_snapshot": { + "name": "agent_config_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "template_id": { + "name": "template_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "template_name": { + "name": "template_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "template_body": { + "name": "template_body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rendered_template_body": { + "name": "rendered_template_body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "harness_issue_description": { + "name": "harness_issue_description", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "output_document_key": { + "name": "output_document_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'output'" + }, + "output_snapshot": { + "name": "output_snapshot", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "superseded_at": { + "name": "superseded_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "harness_issue_expires_at": { + "name": "harness_issue_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "harness_issue_deleted_at": { + "name": "harness_issue_deleted_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": { + "company_skill_test_runs_company_skill_created_idx": { + "name": "company_skill_test_runs_company_skill_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_test_runs_company_issue_idx": { + "name": "company_skill_test_runs_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_test_runs_company_input_created_idx": { + "name": "company_skill_test_runs_company_input_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "input_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_test_runs_company_status_idx": { + "name": "company_skill_test_runs_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_test_runs_company_harness_expires_idx": { + "name": "company_skill_test_runs_company_harness_expires_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "harness_issue_expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_test_runs_company_id_companies_id_fk": { + "name": "company_skill_test_runs_company_id_companies_id_fk", + "tableFrom": "company_skill_test_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_test_runs_skill_id_company_skills_id_fk": { + "name": "company_skill_test_runs_skill_id_company_skills_id_fk", + "tableFrom": "company_skill_test_runs", + "tableTo": "company_skills", + "columnsFrom": [ + "skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_test_runs_input_id_company_skill_test_inputs_id_fk": { + "name": "company_skill_test_runs_input_id_company_skill_test_inputs_id_fk", + "tableFrom": "company_skill_test_runs", + "tableTo": "company_skill_test_inputs", + "columnsFrom": [ + "input_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_skill_test_runs_skill_version_id_company_skill_versions_id_fk": { + "name": "company_skill_test_runs_skill_version_id_company_skill_versions_id_fk", + "tableFrom": "company_skill_test_runs", + "tableTo": "company_skill_versions", + "columnsFrom": [ + "skill_version_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "company_skill_test_runs_agent_id_agents_id_fk": { + "name": "company_skill_test_runs_agent_id_agents_id_fk", + "tableFrom": "company_skill_test_runs", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "company_skill_test_runs_issue_id_issues_id_fk": { + "name": "company_skill_test_runs_issue_id_issues_id_fk", + "tableFrom": "company_skill_test_runs", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_versions": { + "name": "company_skill_versions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_skill_id": { + "name": "company_skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision_number": { + "name": "revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "release_id": { + "name": "release_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "release_name": { + "name": "release_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "released_at": { + "name": "released_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "file_inventory": { + "name": "file_inventory", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skill_versions_skill_revision_idx": { + "name": "company_skill_versions_skill_revision_idx", + "columns": [ + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_versions_skill_release_idx": { + "name": "company_skill_versions_skill_release_idx", + "columns": [ + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "release_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"company_skill_versions\".\"release_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_versions_company_skill_created_idx": { + "name": "company_skill_versions_company_skill_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_versions_company_id_companies_id_fk": { + "name": "company_skill_versions_company_id_companies_id_fk", + "tableFrom": "company_skill_versions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_versions_company_skill_id_company_skills_id_fk": { + "name": "company_skill_versions_company_skill_id_company_skills_id_fk", + "tableFrom": "company_skill_versions", + "tableTo": "company_skills", + "columnsFrom": [ + "company_skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_versions_author_agent_id_agents_id_fk": { + "name": "company_skill_versions_author_agent_id_agents_id_fk", + "tableFrom": "company_skill_versions", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skills": { + "name": "company_skills", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "folder_id": { + "name": "folder_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "markdown": { + "name": "markdown", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_path'" + }, + "source_locator": { + "name": "source_locator", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_ref": { + "name": "source_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trust_level": { + "name": "trust_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown_only'" + }, + "compatibility": { + "name": "compatibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'compatible'" + }, + "file_inventory": { + "name": "file_inventory", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "icon_url": { + "name": "icon_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tagline": { + "name": "tagline", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "author_name": { + "name": "author_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "homepage_url": { + "name": "homepage_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "categories": { + "name": "categories", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "sharing_scope": { + "name": "sharing_scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company'" + }, + "public_share_token": { + "name": "public_share_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "forked_from_skill_id": { + "name": "forked_from_skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "forked_from_company_id": { + "name": "forked_from_company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "star_count": { + "name": "star_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "install_count": { + "name": "install_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "fork_count": { + "name": "fork_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "current_version_id": { + "name": "current_version_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skills_company_key_idx": { + "name": "company_skills_company_key_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skills_company_name_idx": { + "name": "company_skills_company_name_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skills_company_folder_idx": { + "name": "company_skills_company_folder_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skills_company_categories_idx": { + "name": "company_skills_company_categories_idx", + "columns": [ + { + "expression": "categories", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "company_skills_company_sharing_scope_idx": { + "name": "company_skills_company_sharing_scope_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sharing_scope", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skills_company_current_version_idx": { + "name": "company_skills_company_current_version_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "current_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skills_company_forked_from_idx": { + "name": "company_skills_company_forked_from_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "forked_from_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skills_company_id_companies_id_fk": { + "name": "company_skills_company_id_companies_id_fk", + "tableFrom": "company_skills", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "company_skills_folder_id_folders_id_fk": { + "name": "company_skills_folder_id_folders_id_fk", + "tableFrom": "company_skills", + "tableTo": "folders", + "columnsFrom": [ + "folder_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_skills_forked_from_skill_id_company_skills_id_fk": { + "name": "company_skills_forked_from_skill_id_company_skills_id_fk", + "tableFrom": "company_skills", + "tableTo": "company_skills", + "columnsFrom": [ + "forked_from_skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_skills_forked_from_company_id_companies_id_fk": { + "name": "company_skills_forked_from_company_id_companies_id_fk", + "tableFrom": "company_skills", + "tableTo": "companies", + "columnsFrom": [ + "forked_from_company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_skills_current_version_id_company_skill_versions_id_fk": { + "name": "company_skills_current_version_id_company_skill_versions_id_fk", + "tableFrom": "company_skills", + "tableTo": "company_skill_versions", + "columnsFrom": [ + "current_version_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_transfer_runs": { + "name": "company_transfer_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "actor_key": { + "name": "actor_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "container_ref": { + "name": "container_ref", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "manifest_sha256": { + "name": "manifest_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "manifest": { + "name": "manifest", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "chunk_count": { + "name": "chunk_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "blob_count": { + "name": "blob_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "completed_parts": { + "name": "completed_parts", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_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": { + "company_transfer_runs_company_idx": { + "name": "company_transfer_runs_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_transfer_runs_idempotency_direction_idx": { + "name": "company_transfer_runs_idempotency_direction_idx", + "columns": [ + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "direction", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_transfer_runs_actor_status_idx": { + "name": "company_transfer_runs_actor_status_idx", + "columns": [ + { + "expression": "actor_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_transfer_runs_company_id_companies_id_fk": { + "name": "company_transfer_runs_company_id_companies_id_fk", + "tableFrom": "company_transfer_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_user_sidebar_preferences": { + "name": "company_user_sidebar_preferences", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "project_order": { + "name": "project_order", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_user_sidebar_preferences_company_idx": { + "name": "company_user_sidebar_preferences_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_user_sidebar_preferences_user_idx": { + "name": "company_user_sidebar_preferences_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_user_sidebar_preferences_company_user_uq": { + "name": "company_user_sidebar_preferences_company_user_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_user_sidebar_preferences_company_id_companies_id_fk": { + "name": "company_user_sidebar_preferences_company_id_companies_id_fk", + "tableFrom": "company_user_sidebar_preferences", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.completion_contracts": { + "name": "completion_contracts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "schema_version": { + "name": "schema_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "policy_version": { + "name": "policy_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "risk": { + "name": "risk", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "completion_authority": { + "name": "completion_authority", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "incomplete_criteria_policy": { + "name": "incomplete_criteria_policy", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "contract_json": { + "name": "contract_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "canonical_sha256": { + "name": "canonical_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_actor_type": { + "name": "created_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_actor_id": { + "name": "created_by_actor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "supersedes_contract_id": { + "name": "supersedes_contract_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "completion_contracts_issue_revision_uq": { + "name": "completion_contracts_issue_revision_uq", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "completion_contracts_issue_hash_uq": { + "name": "completion_contracts_issue_hash_uq", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "canonical_sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "completion_contracts_company_id_companies_id_fk": { + "name": "completion_contracts_company_id_companies_id_fk", + "tableFrom": "completion_contracts", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "completion_contracts_issue_company_fk": { + "name": "completion_contracts_issue_company_fk", + "tableFrom": "completion_contracts", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "completion_contracts_supersedes_owner_fk": { + "name": "completion_contracts_supersedes_owner_fk", + "tableFrom": "completion_contracts", + "tableTo": "completion_contracts", + "columnsFrom": [ + "company_id", + "issue_id", + "supersedes_contract_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "completion_contracts_company_issue_id_uq": { + "name": "completion_contracts_company_issue_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "issue_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.connection_event_deliveries": { + "name": "connection_event_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_delivery_id": { + "name": "provider_delivery_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event": { + "name": "event", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "installation_id": { + "name": "installation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repository_id": { + "name": "repository_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "normalized_payload": { + "name": "normalized_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_created_at": { + "name": "provider_created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "processed_at": { + "name": "processed_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": { + "connection_event_deliveries_company_provider_id_uq": { + "name": "connection_event_deliveries_company_provider_id_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_delivery_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_event_deliveries_company_status_idx": { + "name": "connection_event_deliveries_company_status_idx", + "columns": [ + { + "expression": "company_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": { + "connection_event_deliveries_company_id_companies_id_fk": { + "name": "connection_event_deliveries_company_id_companies_id_fk", + "tableFrom": "connection_event_deliveries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.connection_intent_deliveries": { + "name": "connection_intent_deliveries", + "schema": "", + "columns": { + "interaction_id": { + "name": "interaction_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "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()" + } + }, + "indexes": { + "connection_intent_deliveries_pending_idx": { + "name": "connection_intent_deliveries_pending_idx", + "columns": [ + { + "expression": "delivered_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "connection_intent_deliveries_interaction_id_issue_thread_interactions_id_fk": { + "name": "connection_intent_deliveries_interaction_id_issue_thread_interactions_id_fk", + "tableFrom": "connection_intent_deliveries", + "tableTo": "issue_thread_interactions", + "columnsFrom": [ + "interaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_intent_deliveries_company_id_companies_id_fk": { + "name": "connection_intent_deliveries_company_id_companies_id_fk", + "tableFrom": "connection_intent_deliveries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cost_events": { + "name": "cost_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "biller": { + "name": "biller", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "billing_type": { + "name": "billing_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "cost_status": { + "name": "cost_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'reported'" + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "input_tokens": { + "name": "input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cached_input_tokens": { + "name": "cached_input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "output_tokens": { + "name": "output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cost_cents": { + "name": "cost_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "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": { + "cost_events_company_occurred_idx": { + "name": "cost_events_company_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_agent_occurred_idx": { + "name": "cost_events_company_agent_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_provider_occurred_idx": { + "name": "cost_events_company_provider_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_biller_occurred_idx": { + "name": "cost_events_company_biller_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "biller", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_heartbeat_run_idx": { + "name": "cost_events_company_heartbeat_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cost_events_company_id_companies_id_fk": { + "name": "cost_events_company_id_companies_id_fk", + "tableFrom": "cost_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_agent_id_agents_id_fk": { + "name": "cost_events_agent_id_agents_id_fk", + "tableFrom": "cost_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_issue_id_issues_id_fk": { + "name": "cost_events_issue_id_issues_id_fk", + "tableFrom": "cost_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cost_events_project_id_projects_id_fk": { + "name": "cost_events_project_id_projects_id_fk", + "tableFrom": "cost_events", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_goal_id_goals_id_fk": { + "name": "cost_events_goal_id_goals_id_fk", + "tableFrom": "cost_events", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "cost_events_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "cost_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.decision_archive_notification_outbox": { + "name": "decision_archive_notification_outbox", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "archive_version": { + "name": "archive_version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "origin_agent_id": { + "name": "origin_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "origin_issue_id": { + "name": "origin_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "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": { + "decision_archive_notification_outbox_uq": { + "name": "decision_archive_notification_outbox_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archive_version", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_archive_notification_outbox_pending_idx": { + "name": "decision_archive_notification_outbox_pending_idx", + "columns": [ + { + "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": { + "decision_archive_notification_outbox_company_id_companies_id_fk": { + "name": "decision_archive_notification_outbox_company_id_companies_id_fk", + "tableFrom": "decision_archive_notification_outbox", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_archive_notification_outbox_origin_agent_id_agents_id_fk": { + "name": "decision_archive_notification_outbox_origin_agent_id_agents_id_fk", + "tableFrom": "decision_archive_notification_outbox", + "tableTo": "agents", + "columnsFrom": [ + "origin_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "decision_archive_notification_outbox_status_check": { + "name": "decision_archive_notification_outbox_status_check", + "value": "\"decision_archive_notification_outbox\".\"status\" IN ('pending', 'delivering', 'delivered')" + } + }, + "isRLSEnabled": false + }, + "public.decision_queue_items": { + "name": "decision_queue_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "added_by_type": { + "name": "added_by_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "added_by_agent_id": { + "name": "added_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "added_by_user_id": { + "name": "added_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "added_by_run_id": { + "name": "added_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "added_by_agent_api_key_id": { + "name": "added_by_agent_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_queue_items_queue_source_uq": { + "name": "decision_queue_items_queue_source_uq", + "columns": [ + { + "expression": "queue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_queue_items_company_source_idx": { + "name": "decision_queue_items_company_source_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_queue_items_company_id_companies_id_fk": { + "name": "decision_queue_items_company_id_companies_id_fk", + "tableFrom": "decision_queue_items", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_queue_items_added_by_agent_id_agents_id_fk": { + "name": "decision_queue_items_added_by_agent_id_agents_id_fk", + "tableFrom": "decision_queue_items", + "tableTo": "agents", + "columnsFrom": [ + "added_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_queue_items_added_by_run_id_heartbeat_runs_id_fk": { + "name": "decision_queue_items_added_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "decision_queue_items", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "added_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_queue_items_added_by_agent_api_key_id_agent_api_keys_id_fk": { + "name": "decision_queue_items_added_by_agent_api_key_id_agent_api_keys_id_fk", + "tableFrom": "decision_queue_items", + "tableTo": "agent_api_keys", + "columnsFrom": [ + "added_by_agent_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_queue_items_queue_company_fk": { + "name": "decision_queue_items_queue_company_fk", + "tableFrom": "decision_queue_items", + "tableTo": "decision_queues", + "columnsFrom": [ + "queue_id", + "company_id" + ], + "columnsTo": [ + "id", + "company_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "decision_queue_items_actor_check": { + "name": "decision_queue_items_actor_check", + "value": "(\n (\"decision_queue_items\".\"added_by_type\" = 'agent' AND \"decision_queue_items\".\"added_by_agent_id\" IS NOT NULL AND \"decision_queue_items\".\"added_by_user_id\" IS NULL)\n OR (\"decision_queue_items\".\"added_by_type\" = 'user' AND \"decision_queue_items\".\"added_by_agent_id\" IS NULL AND \"decision_queue_items\".\"added_by_user_id\" IS NOT NULL)\n OR (\"decision_queue_items\".\"added_by_type\" = 'system' AND \"decision_queue_items\".\"added_by_agent_id\" IS NULL AND \"decision_queue_items\".\"added_by_user_id\" IS NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.decision_queues": { + "name": "decision_queues", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_type": { + "name": "created_by_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_api_key_id": { + "name": "created_by_agent_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "retention_days": { + "name": "retention_days", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "seed_rules": { + "name": "seed_rules", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "seed_rules_enabled": { + "name": "seed_rules_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": { + "decision_queues_company_key_uq": { + "name": "decision_queues_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_queues_company_updated_idx": { + "name": "decision_queues_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_queues_company_id_companies_id_fk": { + "name": "decision_queues_company_id_companies_id_fk", + "tableFrom": "decision_queues", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_queues_created_by_agent_id_agents_id_fk": { + "name": "decision_queues_created_by_agent_id_agents_id_fk", + "tableFrom": "decision_queues", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_queues_created_by_run_id_heartbeat_runs_id_fk": { + "name": "decision_queues_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "decision_queues", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_queues_created_by_agent_api_key_id_agent_api_keys_id_fk": { + "name": "decision_queues_created_by_agent_api_key_id_agent_api_keys_id_fk", + "tableFrom": "decision_queues", + "tableTo": "agent_api_keys", + "columnsFrom": [ + "created_by_agent_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "decision_queues_id_company_uq": { + "name": "decision_queues_id_company_uq", + "nullsNotDistinct": false, + "columns": [ + "id", + "company_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "decision_queues_creator_check": { + "name": "decision_queues_creator_check", + "value": "(\n (\"decision_queues\".\"created_by_type\" = 'agent' AND \"decision_queues\".\"created_by_agent_id\" IS NOT NULL AND \"decision_queues\".\"created_by_user_id\" IS NULL)\n OR (\"decision_queues\".\"created_by_type\" = 'user' AND \"decision_queues\".\"created_by_agent_id\" IS NULL AND \"decision_queues\".\"created_by_user_id\" IS NOT NULL)\n OR (\"decision_queues\".\"created_by_type\" = 'system' AND \"decision_queues\".\"created_by_agent_id\" IS NULL AND \"decision_queues\".\"created_by_user_id\" IS NULL)\n )" + }, + "decision_queues_retention_days_check": { + "name": "decision_queues_retention_days_check", + "value": "\"decision_queues\".\"retention_days\" IS NULL OR (\"decision_queues\".\"retention_days\" >= 1 AND \"decision_queues\".\"retention_days\" <= 3650)" + } + }, + "isRLSEnabled": false + }, + "public.decision_retention": { + "name": "decision_retention", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_activity_at": { + "name": "source_activity_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "keep": { + "name": "keep", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "archived_reason": { + "name": "archived_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_by_type": { + "name": "archived_by_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_by_agent_id": { + "name": "archived_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "archived_by_user_id": { + "name": "archived_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_by_run_id": { + "name": "archived_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "archive_version": { + "name": "archive_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "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": { + "decision_retention_company_source_uq": { + "name": "decision_retention_company_source_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_retention_company_archived_idx": { + "name": "decision_retention_company_archived_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_retention_company_id_companies_id_fk": { + "name": "decision_retention_company_id_companies_id_fk", + "tableFrom": "decision_retention", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_retention_archived_by_agent_id_agents_id_fk": { + "name": "decision_retention_archived_by_agent_id_agents_id_fk", + "tableFrom": "decision_retention", + "tableTo": "agents", + "columnsFrom": [ + "archived_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_retention_archived_by_run_id_heartbeat_runs_id_fk": { + "name": "decision_retention_archived_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "decision_retention", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "archived_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "decision_retention_archive_actor_check": { + "name": "decision_retention_archive_actor_check", + "value": "(\n (\"decision_retention\".\"archived_at\" IS NULL AND \"decision_retention\".\"archived_by_type\" IS NULL AND \"decision_retention\".\"archived_by_agent_id\" IS NULL AND \"decision_retention\".\"archived_by_user_id\" IS NULL)\n OR (\"decision_retention\".\"archived_at\" IS NOT NULL AND \"decision_retention\".\"archived_by_type\" = 'system' AND \"decision_retention\".\"archived_by_agent_id\" IS NULL AND \"decision_retention\".\"archived_by_user_id\" IS NULL)\n OR (\"decision_retention\".\"archived_at\" IS NOT NULL AND \"decision_retention\".\"archived_by_type\" = 'agent' AND \"decision_retention\".\"archived_by_agent_id\" IS NOT NULL AND \"decision_retention\".\"archived_by_user_id\" IS NULL)\n OR (\"decision_retention\".\"archived_at\" IS NOT NULL AND \"decision_retention\".\"archived_by_type\" = 'user' AND \"decision_retention\".\"archived_by_agent_id\" IS NULL AND \"decision_retention\".\"archived_by_user_id\" IS NOT NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.decision_triage": { + "name": "decision_triage", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "decide_by": { + "name": "decide_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decide_by_date": { + "name": "decide_by_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "snoozed_until": { + "name": "snoozed_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "set_by_type": { + "name": "set_by_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "set_by_agent_id": { + "name": "set_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "set_by_user_id": { + "name": "set_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "set_by_run_id": { + "name": "set_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "set_by_agent_api_key_id": { + "name": "set_by_agent_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "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": { + "decision_triage_company_source_uq": { + "name": "decision_triage_company_source_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_triage_company_decide_by_idx": { + "name": "decision_triage_company_decide_by_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "decide_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_triage_company_id_companies_id_fk": { + "name": "decision_triage_company_id_companies_id_fk", + "tableFrom": "decision_triage", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_triage_set_by_agent_id_agents_id_fk": { + "name": "decision_triage_set_by_agent_id_agents_id_fk", + "tableFrom": "decision_triage", + "tableTo": "agents", + "columnsFrom": [ + "set_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_triage_set_by_run_id_heartbeat_runs_id_fk": { + "name": "decision_triage_set_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "decision_triage", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "set_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_triage_set_by_agent_api_key_id_agent_api_keys_id_fk": { + "name": "decision_triage_set_by_agent_api_key_id_agent_api_keys_id_fk", + "tableFrom": "decision_triage", + "tableTo": "agent_api_keys", + "columnsFrom": [ + "set_by_agent_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "decision_triage_actor_check": { + "name": "decision_triage_actor_check", + "value": "(\n (\"decision_triage\".\"set_by_type\" = 'agent' AND \"decision_triage\".\"set_by_agent_id\" IS NOT NULL AND \"decision_triage\".\"set_by_user_id\" IS NULL)\n OR (\"decision_triage\".\"set_by_type\" = 'user' AND \"decision_triage\".\"set_by_agent_id\" IS NULL AND \"decision_triage\".\"set_by_user_id\" IS NOT NULL)\n )" + }, + "decision_triage_decide_by_check": { + "name": "decision_triage_decide_by_check", + "value": "(\n (\"decision_triage\".\"decide_by\" IS NULL AND \"decision_triage\".\"decide_by_date\" IS NULL)\n OR (\"decision_triage\".\"decide_by\" IN ('today', 'this_week', 'whenever') AND \"decision_triage\".\"decide_by_date\" IS NULL)\n OR (\"decision_triage\".\"decide_by\" = 'date' AND \"decision_triage\".\"decide_by_date\" IS NOT NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.decision_triage_events": { + "name": "decision_triage_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_agent_id": { + "name": "actor_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_run_id": { + "name": "actor_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "agent_api_key_id": { + "name": "agent_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "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": { + "decision_triage_events_company_source_created_idx": { + "name": "decision_triage_events_company_source_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_triage_events_queue_created_idx": { + "name": "decision_triage_events_queue_created_idx", + "columns": [ + { + "expression": "queue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_triage_events_company_id_companies_id_fk": { + "name": "decision_triage_events_company_id_companies_id_fk", + "tableFrom": "decision_triage_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_triage_events_queue_id_decision_queues_id_fk": { + "name": "decision_triage_events_queue_id_decision_queues_id_fk", + "tableFrom": "decision_triage_events", + "tableTo": "decision_queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_triage_events_actor_agent_id_agents_id_fk": { + "name": "decision_triage_events_actor_agent_id_agents_id_fk", + "tableFrom": "decision_triage_events", + "tableTo": "agents", + "columnsFrom": [ + "actor_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_triage_events_actor_run_id_heartbeat_runs_id_fk": { + "name": "decision_triage_events_actor_run_id_heartbeat_runs_id_fk", + "tableFrom": "decision_triage_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "actor_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_triage_events_agent_api_key_id_agent_api_keys_id_fk": { + "name": "decision_triage_events_agent_api_key_id_agent_api_keys_id_fk", + "tableFrom": "decision_triage_events", + "tableTo": "agent_api_keys", + "columnsFrom": [ + "agent_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "decision_triage_events_actor_check": { + "name": "decision_triage_events_actor_check", + "value": "(\n (\"decision_triage_events\".\"actor_type\" = 'agent' AND \"decision_triage_events\".\"actor_agent_id\" IS NOT NULL AND \"decision_triage_events\".\"actor_user_id\" IS NULL)\n OR (\"decision_triage_events\".\"actor_type\" = 'user' AND \"decision_triage_events\".\"actor_agent_id\" IS NULL AND \"decision_triage_events\".\"actor_user_id\" IS NOT NULL)\n OR (\"decision_triage_events\".\"actor_type\" = 'system' AND \"decision_triage_events\".\"actor_agent_id\" IS NULL AND \"decision_triage_events\".\"actor_user_id\" IS NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.decision_training_examples": { + "name": "decision_training_examples", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "cutoff_at": { + "name": "cutoff_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "notes_history": { + "name": "notes_history", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "decision_outcome": { + "name": "decision_outcome", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "retention_policy": { + "name": "retention_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'scrub_deleted_comments_v1'" + }, + "snapshot": { + "name": "snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_training_examples_company_created_at_idx": { + "name": "decision_training_examples_company_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_training_examples_issue_idx": { + "name": "decision_training_examples_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_training_examples_source_author_uq": { + "name": "decision_training_examples_source_author_uq", + "columns": [ + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_training_examples_company_id_companies_id_fk": { + "name": "decision_training_examples_company_id_companies_id_fk", + "tableFrom": "decision_training_examples", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_training_examples_issue_id_issues_id_fk": { + "name": "decision_training_examples_issue_id_issues_id_fk", + "tableFrom": "decision_training_examples", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.decision_bundles": { + "name": "decision_bundles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_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 + }, + "origin_agent_id": { + "name": "origin_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "origin_issue_id": { + "name": "origin_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "origin_run_id": { + "name": "origin_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_bundles_company_created_at_idx": { + "name": "decision_bundles_company_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_bundles_company_id_companies_id_fk": { + "name": "decision_bundles_company_id_companies_id_fk", + "tableFrom": "decision_bundles", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_bundles_origin_agent_id_agents_id_fk": { + "name": "decision_bundles_origin_agent_id_agents_id_fk", + "tableFrom": "decision_bundles", + "tableTo": "agents", + "columnsFrom": [ + "origin_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_bundles_origin_issue_id_issues_id_fk": { + "name": "decision_bundles_origin_issue_id_issues_id_fk", + "tableFrom": "decision_bundles", + "tableTo": "issues", + "columnsFrom": [ + "origin_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_bundles_origin_run_id_heartbeat_runs_id_fk": { + "name": "decision_bundles_origin_run_id_heartbeat_runs_id_fk", + "tableFrom": "decision_bundles", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "origin_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.decision_effect_executions": { + "name": "decision_effect_executions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "decision_id": { + "name": "decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "effect_index": { + "name": "effect_index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "effect_type": { + "name": "effect_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_issue_id": { + "name": "target_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'claimed'" + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "activity_log_id": { + "name": "activity_log_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "executed_at": { + "name": "executed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "decision_effect_executions_decision_effect_uq": { + "name": "decision_effect_executions_decision_effect_uq", + "columns": [ + { + "expression": "decision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "effect_index", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_effect_executions_target_issue_idx": { + "name": "decision_effect_executions_target_issue_idx", + "columns": [ + { + "expression": "target_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_effect_executions_decision_id_decisions_id_fk": { + "name": "decision_effect_executions_decision_id_decisions_id_fk", + "tableFrom": "decision_effect_executions", + "tableTo": "decisions", + "columnsFrom": [ + "decision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_effect_executions_target_issue_id_issues_id_fk": { + "name": "decision_effect_executions_target_issue_id_issues_id_fk", + "tableFrom": "decision_effect_executions", + "tableTo": "issues", + "columnsFrom": [ + "target_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_effect_executions_activity_log_id_activity_log_id_fk": { + "name": "decision_effect_executions_activity_log_id_activity_log_id_fk", + "tableFrom": "decision_effect_executions", + "tableTo": "activity_log", + "columnsFrom": [ + "activity_log_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.decision_target_issues": { + "name": "decision_target_issues", + "schema": "", + "columns": { + "decision_id": { + "name": "decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "decision_target_issues_decision_idx": { + "name": "decision_target_issues_decision_idx", + "columns": [ + { + "expression": "decision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_target_issues_issue_idx": { + "name": "decision_target_issues_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_target_issues_decision_id_decisions_id_fk": { + "name": "decision_target_issues_decision_id_decisions_id_fk", + "tableFrom": "decision_target_issues", + "tableTo": "decisions", + "columnsFrom": [ + "decision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_target_issues_issue_id_issues_id_fk": { + "name": "decision_target_issues_issue_id_issues_id_fk", + "tableFrom": "decision_target_issues", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_target_issues_company_id_companies_id_fk": { + "name": "decision_target_issues_company_id_companies_id_fk", + "tableFrom": "decision_target_issues", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "decision_target_issues_decision_id_issue_id_pk": { + "name": "decision_target_issues_decision_id_issue_id_pk", + "columns": [ + "decision_id", + "issue_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.decisions": { + "name": "decisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "bundle_id": { + "name": "bundle_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "origin_agent_id": { + "name": "origin_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "origin_issue_id": { + "name": "origin_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "origin_run_id": { + "name": "origin_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "rule_key": { + "name": "rule_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "options": { + "name": "options", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "inputs": { + "name": "inputs", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "execution_status": { + "name": "execution_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "chosen_option_id": { + "name": "chosen_option_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "input_values": { + "name": "input_values", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "decided_by_user_id": { + "name": "decided_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_at": { + "name": "decided_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "signed_spec": { + "name": "signed_spec", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_snapshots": { + "name": "target_snapshots", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "continuation_policy": { + "name": "continuation_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "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()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decisions_company_status_expires_at_idx": { + "name": "decisions_company_status_expires_at_idx", + "columns": [ + { + "expression": "company_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": {} + }, + "decisions_bundle_idx": { + "name": "decisions_bundle_idx", + "columns": [ + { + "expression": "bundle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decisions_origin_issue_idx": { + "name": "decisions_origin_issue_idx", + "columns": [ + { + "expression": "origin_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decisions_company_idempotency_uq": { + "name": "decisions_company_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"decisions\".\"idempotency_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decisions_company_id_companies_id_fk": { + "name": "decisions_company_id_companies_id_fk", + "tableFrom": "decisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decisions_bundle_id_decision_bundles_id_fk": { + "name": "decisions_bundle_id_decision_bundles_id_fk", + "tableFrom": "decisions", + "tableTo": "decision_bundles", + "columnsFrom": [ + "bundle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "decisions_origin_agent_id_agents_id_fk": { + "name": "decisions_origin_agent_id_agents_id_fk", + "tableFrom": "decisions", + "tableTo": "agents", + "columnsFrom": [ + "origin_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decisions_origin_issue_id_issues_id_fk": { + "name": "decisions_origin_issue_id_issues_id_fk", + "tableFrom": "decisions", + "tableTo": "issues", + "columnsFrom": [ + "origin_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decisions_origin_run_id_heartbeat_runs_id_fk": { + "name": "decisions_origin_run_id_heartbeat_runs_id_fk", + "tableFrom": "decisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "origin_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.document_annotation_anchor_snapshots": { + "name": "document_annotation_anchor_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "thread_id": { + "name": "thread_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "from_revision_id": { + "name": "from_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "from_revision_number": { + "name": "from_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "to_revision_id": { + "name": "to_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "to_revision_number": { + "name": "to_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "previous_anchor": { + "name": "previous_anchor", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "next_anchor": { + "name": "next_anchor", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "anchor_state": { + "name": "anchor_state", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "anchor_confidence": { + "name": "anchor_confidence", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "document_annotation_anchor_snapshots_company_thread_created_at_idx": { + "name": "document_annotation_anchor_snapshots_company_thread_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "thread_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_anchor_snapshots_company_document_revision_idx": { + "name": "document_annotation_anchor_snapshots_company_document_revision_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "to_revision_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_annotation_anchor_snapshots_company_id_companies_id_fk": { + "name": "document_annotation_anchor_snapshots_company_id_companies_id_fk", + "tableFrom": "document_annotation_anchor_snapshots", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "document_annotation_anchor_snapshots_thread_id_document_annotation_threads_id_fk": { + "name": "document_annotation_anchor_snapshots_thread_id_document_annotation_threads_id_fk", + "tableFrom": "document_annotation_anchor_snapshots", + "tableTo": "document_annotation_threads", + "columnsFrom": [ + "thread_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_anchor_snapshots_document_id_documents_id_fk": { + "name": "document_annotation_anchor_snapshots_document_id_documents_id_fk", + "tableFrom": "document_annotation_anchor_snapshots", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_anchor_snapshots_from_revision_id_document_revisions_id_fk": { + "name": "document_annotation_anchor_snapshots_from_revision_id_document_revisions_id_fk", + "tableFrom": "document_annotation_anchor_snapshots", + "tableTo": "document_revisions", + "columnsFrom": [ + "from_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_annotation_anchor_snapshots_to_revision_id_document_revisions_id_fk": { + "name": "document_annotation_anchor_snapshots_to_revision_id_document_revisions_id_fk", + "tableFrom": "document_annotation_anchor_snapshots", + "tableTo": "document_revisions", + "columnsFrom": [ + "to_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.document_annotation_comments": { + "name": "document_annotation_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "thread_id": { + "name": "thread_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_type": { + "name": "author_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_comment_id": { + "name": "issue_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_trust": { + "name": "source_trust", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "document_annotation_comments_company_thread_created_at_idx": { + "name": "document_annotation_comments_company_thread_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "thread_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_comments_company_issue_created_at_idx": { + "name": "document_annotation_comments_company_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_comments_company_routine_created_at_idx": { + "name": "document_annotation_comments_company_routine_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_comments_company_case_created_at_idx": { + "name": "document_annotation_comments_company_case_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_comments_company_document_created_at_idx": { + "name": "document_annotation_comments_company_document_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_comments_issue_comment_idx": { + "name": "document_annotation_comments_issue_comment_idx", + "columns": [ + { + "expression": "issue_comment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_comments_body_search_idx": { + "name": "document_annotation_comments_body_search_idx", + "columns": [ + { + "expression": "body", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "document_annotation_comments_company_id_companies_id_fk": { + "name": "document_annotation_comments_company_id_companies_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "document_annotation_comments_thread_id_document_annotation_threads_id_fk": { + "name": "document_annotation_comments_thread_id_document_annotation_threads_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "document_annotation_threads", + "columnsFrom": [ + "thread_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_comments_issue_id_issues_id_fk": { + "name": "document_annotation_comments_issue_id_issues_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_comments_routine_id_routines_id_fk": { + "name": "document_annotation_comments_routine_id_routines_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_comments_case_id_cases_id_fk": { + "name": "document_annotation_comments_case_id_cases_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_comments_document_id_documents_id_fk": { + "name": "document_annotation_comments_document_id_documents_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_comments_author_agent_id_agents_id_fk": { + "name": "document_annotation_comments_author_agent_id_agents_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_annotation_comments_created_by_run_id_heartbeat_runs_id_fk": { + "name": "document_annotation_comments_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_annotation_comments_issue_comment_id_issue_comments_id_fk": { + "name": "document_annotation_comments_issue_comment_id_issue_comments_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "issue_comments", + "columnsFrom": [ + "issue_comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "document_annotation_comments_exactly_one_owner_chk": { + "name": "document_annotation_comments_exactly_one_owner_chk", + "value": "num_nonnulls(\"document_annotation_comments\".\"issue_id\", \"document_annotation_comments\".\"routine_id\", \"document_annotation_comments\".\"case_id\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.document_annotation_threads": { + "name": "document_annotation_threads", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_key": { + "name": "document_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "anchor_state": { + "name": "anchor_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "original_revision_id": { + "name": "original_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "original_revision_number": { + "name": "original_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "current_revision_id": { + "name": "current_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "current_revision_number": { + "name": "current_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "selected_text": { + "name": "selected_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prefix_text": { + "name": "prefix_text", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "suffix_text": { + "name": "suffix_text", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "normalized_start": { + "name": "normalized_start", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "normalized_end": { + "name": "normalized_end", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "markdown_start": { + "name": "markdown_start", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "markdown_end": { + "name": "markdown_end", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "anchor_confidence": { + "name": "anchor_confidence", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'exact'" + }, + "anchor_selector": { + "name": "anchor_selector", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_by_agent_id": { + "name": "resolved_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_by_user_id": { + "name": "resolved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "document_annotation_threads_company_document_status_idx": { + "name": "document_annotation_threads_company_document_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_threads_company_issue_status_idx": { + "name": "document_annotation_threads_company_issue_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_threads_company_routine_status_idx": { + "name": "document_annotation_threads_company_routine_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_threads_company_case_status_idx": { + "name": "document_annotation_threads_company_case_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_threads_company_current_revision_open_idx": { + "name": "document_annotation_threads_company_current_revision_open_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "current_revision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_threads_company_anchor_state_idx": { + "name": "document_annotation_threads_company_anchor_state_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "anchor_state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_annotation_threads_company_id_companies_id_fk": { + "name": "document_annotation_threads_company_id_companies_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "document_annotation_threads_issue_id_issues_id_fk": { + "name": "document_annotation_threads_issue_id_issues_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_threads_routine_id_routines_id_fk": { + "name": "document_annotation_threads_routine_id_routines_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_threads_case_id_cases_id_fk": { + "name": "document_annotation_threads_case_id_cases_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_threads_document_id_documents_id_fk": { + "name": "document_annotation_threads_document_id_documents_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_threads_original_revision_id_document_revisions_id_fk": { + "name": "document_annotation_threads_original_revision_id_document_revisions_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "document_revisions", + "columnsFrom": [ + "original_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_annotation_threads_current_revision_id_document_revisions_id_fk": { + "name": "document_annotation_threads_current_revision_id_document_revisions_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "document_revisions", + "columnsFrom": [ + "current_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_annotation_threads_created_by_agent_id_agents_id_fk": { + "name": "document_annotation_threads_created_by_agent_id_agents_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_annotation_threads_resolved_by_agent_id_agents_id_fk": { + "name": "document_annotation_threads_resolved_by_agent_id_agents_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "agents", + "columnsFrom": [ + "resolved_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "document_annotation_threads_exactly_one_owner_chk": { + "name": "document_annotation_threads_exactly_one_owner_chk", + "value": "num_nonnulls(\"document_annotation_threads\".\"issue_id\", \"document_annotation_threads\".\"routine_id\", \"document_annotation_threads\".\"case_id\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.document_memberships": { + "name": "document_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "starred_at": { + "name": "starred_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": { + "document_memberships_company_user_starred_idx": { + "name": "document_memberships_company_user_starred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "starred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_memberships_company_user_document_uq": { + "name": "document_memberships_company_user_document_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_memberships_company_id_companies_id_fk": { + "name": "document_memberships_company_id_companies_id_fk", + "tableFrom": "document_memberships", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_memberships_document_id_documents_id_fk": { + "name": "document_memberships_document_id_documents_id_fk", + "tableFrom": "document_memberships", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.document_revisions": { + "name": "document_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision_number": { + "name": "revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown'" + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "change_summary": { + "name": "change_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "document_revisions_document_revision_uq": { + "name": "document_revisions_document_revision_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_revisions_company_document_created_idx": { + "name": "document_revisions_company_document_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_revisions_company_id_companies_id_fk": { + "name": "document_revisions_company_id_companies_id_fk", + "tableFrom": "document_revisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_revisions_document_id_documents_id_fk": { + "name": "document_revisions_document_id_documents_id_fk", + "tableFrom": "document_revisions", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_revisions_created_by_agent_id_agents_id_fk": { + "name": "document_revisions_created_by_agent_id_agents_id_fk", + "tableFrom": "document_revisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_revisions_created_by_run_id_heartbeat_runs_id_fk": { + "name": "document_revisions_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "document_revisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.documents": { + "name": "documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown'" + }, + "latest_body": { + "name": "latest_body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "latest_revision_id": { + "name": "latest_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "latest_revision_number": { + "name": "latest_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "locked_at": { + "name": "locked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "locked_by_agent_id": { + "name": "locked_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "locked_by_user_id": { + "name": "locked_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_trust": { + "name": "source_trust", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "documents_company_updated_idx": { + "name": "documents_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "documents_company_created_idx": { + "name": "documents_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "documents_title_search_idx": { + "name": "documents_title_search_idx", + "columns": [ + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "documents_latest_body_search_idx": { + "name": "documents_latest_body_search_idx", + "columns": [ + { + "expression": "latest_body", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "documents_company_id_companies_id_fk": { + "name": "documents_company_id_companies_id_fk", + "tableFrom": "documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "documents_created_by_agent_id_agents_id_fk": { + "name": "documents_created_by_agent_id_agents_id_fk", + "tableFrom": "documents", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "documents_updated_by_agent_id_agents_id_fk": { + "name": "documents_updated_by_agent_id_agents_id_fk", + "tableFrom": "documents", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "documents_locked_by_agent_id_agents_id_fk": { + "name": "documents_locked_by_agent_id_agents_id_fk", + "tableFrom": "documents", + "tableTo": "agents", + "columnsFrom": [ + "locked_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.email_endpoints": { + "name": "email_endpoints", + "schema": "", + "columns": { + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "receive_mode": { + "name": "receive_mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "webhook_id": { + "name": "webhook_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owned_api_key_id": { + "name": "owned_api_key_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "activation_at": { + "name": "activation_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "sync_checkpoint": { + "name": "sync_checkpoint", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "email_endpoints_company_id_endpoint_id_chat_endpoints_company_id_id_fk": { + "name": "email_endpoints_company_id_endpoint_id_chat_endpoints_company_id_id_fk", + "tableFrom": "email_endpoints", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "email_endpoints_receive_mode_check": { + "name": "email_endpoints_receive_mode_check", + "value": "\"email_endpoints\".\"receive_mode\" in ('websocket', 'webhook')" + } + }, + "isRLSEnabled": false + }, + "public.email_messages": { + "name": "email_messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider_message_id": { + "name": "provider_message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "envelope": { + "name": "envelope", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "full_text": { + "name": "full_text", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "automatic": { + "name": "automatic", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "attachment_ids": { + "name": "attachment_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "email_messages_provider_uq": { + "name": "email_messages_provider_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "email_messages_conversation_idx": { + "name": "email_messages_conversation_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "timestamp", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "email_messages_company_id_endpoint_id_chat_endpoints_company_id_id_fk": { + "name": "email_messages_company_id_endpoint_id_chat_endpoints_company_id_id_fk", + "tableFrom": "email_messages", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "email_messages_company_id_conversation_id_chat_conversations_company_id_id_fk": { + "name": "email_messages_company_id_conversation_id_chat_conversations_company_id_id_fk", + "tableFrom": "email_messages", + "tableTo": "chat_conversations", + "columnsFrom": [ + "company_id", + "conversation_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "email_messages_direction_check": { + "name": "email_messages_direction_check", + "value": "\"email_messages\".\"direction\" in ('inbound', 'outbound')" + } + }, + "isRLSEnabled": false + }, + "public.email_sends": { + "name": "email_sends", + "schema": "", + "columns": { + "publication_id": { + "name": "publication_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "request": { + "name": "request", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "actor": { + "name": "actor", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "digest": { + "name": "digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "first_attempt_at": { + "name": "first_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "email_sends_pending_idx": { + "name": "email_sends_pending_idx", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "outcome", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"email_sends\".\"outcome\" in ('queued', 'uncertain')", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "email_sends_company_id_endpoint_id_chat_endpoints_company_id_id_fk": { + "name": "email_sends_company_id_endpoint_id_chat_endpoints_company_id_id_fk", + "tableFrom": "email_sends", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "email_sends_company_id_publication_id_chat_publications_company_id_id_fk": { + "name": "email_sends_company_id_publication_id_chat_publications_company_id_id_fk", + "tableFrom": "email_sends", + "tableTo": "chat_publications", + "columnsFrom": [ + "company_id", + "publication_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "email_sends_outcome_check": { + "name": "email_sends_outcome_check", + "value": "\"email_sends\".\"outcome\" in ('queued', 'sent', 'delivered', 'failed', 'uncertain')" + } + }, + "isRLSEnabled": false + }, + "public.environment_custom_image_setup_sessions": { + "name": "environment_custom_image_setup_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "environment_id": { + "name": "environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "template_id": { + "name": "template_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "promoted_template_id": { + "name": "promoted_template_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_lease_id": { + "name": "provider_lease_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "environment_lease_id": { + "name": "environment_lease_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'starting'" + }, + "started_by_user_id": { + "name": "started_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_by_agent_id": { + "name": "started_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "base_template_ref": { + "name": "base_template_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "connection_summary": { + "name": "connection_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "connection_secret_ref": { + "name": "connection_secret_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "environment_custom_image_setup_sessions_environment_status_idx": { + "name": "environment_custom_image_setup_sessions_environment_status_idx", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_setup_sessions_environment_active_uq": { + "name": "environment_custom_image_setup_sessions_environment_active_uq", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"environment_custom_image_setup_sessions\".\"status\" IN ('starting', 'waiting_for_user', 'capturing')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_setup_sessions_template_idx": { + "name": "environment_custom_image_setup_sessions_template_idx", + "columns": [ + { + "expression": "template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_setup_sessions_promoted_template_idx": { + "name": "environment_custom_image_setup_sessions_promoted_template_idx", + "columns": [ + { + "expression": "promoted_template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_setup_sessions_expires_idx": { + "name": "environment_custom_image_setup_sessions_expires_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_setup_sessions_provider_lease_idx": { + "name": "environment_custom_image_setup_sessions_provider_lease_idx", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_lease_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "environment_custom_image_setup_sessions_environment_id_environments_id_fk": { + "name": "environment_custom_image_setup_sessions_environment_id_environments_id_fk", + "tableFrom": "environment_custom_image_setup_sessions", + "tableTo": "environments", + "columnsFrom": [ + "environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "environment_custom_image_setup_sessions_template_id_environment_custom_image_templates_id_fk": { + "name": "environment_custom_image_setup_sessions_template_id_environment_custom_image_templates_id_fk", + "tableFrom": "environment_custom_image_setup_sessions", + "tableTo": "environment_custom_image_templates", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_custom_image_setup_sessions_promoted_template_id_environment_custom_image_templates_id_fk": { + "name": "environment_custom_image_setup_sessions_promoted_template_id_environment_custom_image_templates_id_fk", + "tableFrom": "environment_custom_image_setup_sessions", + "tableTo": "environment_custom_image_templates", + "columnsFrom": [ + "promoted_template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_custom_image_setup_sessions_environment_lease_id_environment_leases_id_fk": { + "name": "environment_custom_image_setup_sessions_environment_lease_id_environment_leases_id_fk", + "tableFrom": "environment_custom_image_setup_sessions", + "tableTo": "environment_leases", + "columnsFrom": [ + "environment_lease_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_custom_image_setup_sessions_started_by_agent_id_agents_id_fk": { + "name": "environment_custom_image_setup_sessions_started_by_agent_id_agents_id_fk", + "tableFrom": "environment_custom_image_setup_sessions", + "tableTo": "agents", + "columnsFrom": [ + "started_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.environment_custom_image_templates": { + "name": "environment_custom_image_templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "environment_id": { + "name": "environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "template_kind": { + "name": "template_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "template_ref": { + "name": "template_ref", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_template_ref": { + "name": "source_template_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_environment_config_fingerprint": { + "name": "source_environment_config_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "captured_at": { + "name": "captured_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "superseded_by_template_id": { + "name": "superseded_by_template_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "environment_custom_image_templates_environment_status_idx": { + "name": "environment_custom_image_templates_environment_status_idx", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_templates_environment_provider_status_idx": { + "name": "environment_custom_image_templates_environment_provider_status_idx", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_templates_environment_active_uq": { + "name": "environment_custom_image_templates_environment_active_uq", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"environment_custom_image_templates\".\"status\" = 'active'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_templates_superseded_by_idx": { + "name": "environment_custom_image_templates_superseded_by_idx", + "columns": [ + { + "expression": "superseded_by_template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_templates_last_used_idx": { + "name": "environment_custom_image_templates_last_used_idx", + "columns": [ + { + "expression": "last_used_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "environment_custom_image_templates_environment_id_environments_id_fk": { + "name": "environment_custom_image_templates_environment_id_environments_id_fk", + "tableFrom": "environment_custom_image_templates", + "tableTo": "environments", + "columnsFrom": [ + "environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "environment_custom_image_templates_created_by_agent_id_agents_id_fk": { + "name": "environment_custom_image_templates_created_by_agent_id_agents_id_fk", + "tableFrom": "environment_custom_image_templates", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_custom_image_templates_superseded_by_template_id_environment_custom_image_templates_id_fk": { + "name": "environment_custom_image_templates_superseded_by_template_id_environment_custom_image_templates_id_fk", + "tableFrom": "environment_custom_image_templates", + "tableTo": "environment_custom_image_templates", + "columnsFrom": [ + "superseded_by_template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.environment_leases": { + "name": "environment_leases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "environment_id": { + "name": "environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "lease_policy": { + "name": "lease_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'ephemeral'" + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_lease_id": { + "name": "provider_lease_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "acquired_at": { + "name": "acquired_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": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "released_at": { + "name": "released_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cleanup_status": { + "name": "cleanup_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "environment_leases_company_environment_status_idx": { + "name": "environment_leases_company_environment_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_leases_company_execution_workspace_idx": { + "name": "environment_leases_company_execution_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_leases_company_issue_idx": { + "name": "environment_leases_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_leases_heartbeat_run_idx": { + "name": "environment_leases_heartbeat_run_idx", + "columns": [ + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_leases_company_last_used_idx": { + "name": "environment_leases_company_last_used_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_used_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_leases_provider_lease_idx": { + "name": "environment_leases_provider_lease_idx", + "columns": [ + { + "expression": "provider_lease_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "environment_leases_company_id_companies_id_fk": { + "name": "environment_leases_company_id_companies_id_fk", + "tableFrom": "environment_leases", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "environment_leases_environment_id_environments_id_fk": { + "name": "environment_leases_environment_id_environments_id_fk", + "tableFrom": "environment_leases", + "tableTo": "environments", + "columnsFrom": [ + "environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_leases_execution_workspace_id_execution_workspaces_id_fk": { + "name": "environment_leases_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "environment_leases", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_leases_issue_id_issues_id_fk": { + "name": "environment_leases_issue_id_issues_id_fk", + "tableFrom": "environment_leases", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_leases_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "environment_leases_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "environment_leases", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.environments": { + "name": "environments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "driver": { + "name": "driver", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "env_vars": { + "name": "env_vars", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "environments_status_idx": { + "name": "environments_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environments_local_driver_idx": { + "name": "environments_local_driver_idx", + "columns": [ + { + "expression": "driver", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"environments\".\"driver\" = 'local'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "environments_managed_sandbox_idx": { + "name": "environments_managed_sandbox_idx", + "columns": [ + { + "expression": "driver", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"environments\".\"driver\" = 'sandbox' AND (\"environments\".\"metadata\" ->> 'managedByPaperclip')::boolean = true", + "concurrently": false, + "method": "btree", + "with": {} + }, + "environments_name_idx": { + "name": "environments_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_workspace_runtime_leases": { + "name": "execution_workspace_runtime_leases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "owner_key": { + "name": "owner_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_issue_id": { + "name": "owner_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_run_id": { + "name": "owner_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_action": { + "name": "last_action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "renewed_at": { + "name": "renewed_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 + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_workspace_runtime_leases_company_workspace_idx": { + "name": "execution_workspace_runtime_leases_company_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspace_runtime_leases_company_owner_idx": { + "name": "execution_workspace_runtime_leases_company_owner_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspace_runtime_leases_expires_at_idx": { + "name": "execution_workspace_runtime_leases_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_workspace_runtime_leases_company_id_companies_id_fk": { + "name": "execution_workspace_runtime_leases_company_id_companies_id_fk", + "tableFrom": "execution_workspace_runtime_leases", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_workspace_runtime_leases_execution_workspace_id_execution_workspaces_id_fk": { + "name": "execution_workspace_runtime_leases_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "execution_workspace_runtime_leases", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_workspace_runtime_leases_owner_issue_id_issues_id_fk": { + "name": "execution_workspace_runtime_leases_owner_issue_id_issues_id_fk", + "tableFrom": "execution_workspace_runtime_leases", + "tableTo": "issues", + "columnsFrom": [ + "owner_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspace_runtime_leases_owner_run_id_heartbeat_runs_id_fk": { + "name": "execution_workspace_runtime_leases_owner_run_id_heartbeat_runs_id_fk", + "tableFrom": "execution_workspace_runtime_leases", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "owner_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspace_runtime_leases_owner_agent_id_agents_id_fk": { + "name": "execution_workspace_runtime_leases_owner_agent_id_agents_id_fk", + "tableFrom": "execution_workspace_runtime_leases", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "execution_workspace_runtime_leases_execution_workspace_id_unique": { + "name": "execution_workspace_runtime_leases_execution_workspace_id_unique", + "nullsNotDistinct": false, + "columns": [ + "execution_workspace_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_workspaces": { + "name": "execution_workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "strategy_type": { + "name": "strategy_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_url": { + "name": "repo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "base_ref": { + "name": "base_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "branch_name": { + "name": "branch_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_type": { + "name": "provider_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_fs'" + }, + "provider_ref": { + "name": "provider_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "derived_from_execution_workspace_id": { + "name": "derived_from_execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "opened_at": { + "name": "opened_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 + }, + "cleanup_eligible_at": { + "name": "cleanup_eligible_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cleanup_reason": { + "name": "cleanup_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_workspaces_company_project_status_idx": { + "name": "execution_workspaces_company_project_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_project_workspace_status_idx": { + "name": "execution_workspaces_company_project_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_source_issue_idx": { + "name": "execution_workspaces_company_source_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_last_used_idx": { + "name": "execution_workspaces_company_last_used_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_used_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_branch_idx": { + "name": "execution_workspaces_company_branch_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "branch_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_workspaces_company_id_companies_id_fk": { + "name": "execution_workspaces_company_id_companies_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_workspaces_project_id_projects_id_fk": { + "name": "execution_workspaces_project_id_projects_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_workspaces_project_workspace_id_project_workspaces_id_fk": { + "name": "execution_workspaces_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspaces_source_issue_id_issues_id_fk": { + "name": "execution_workspaces_source_issue_id_issues_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspaces_derived_from_execution_workspace_id_execution_workspaces_id_fk": { + "name": "execution_workspaces_derived_from_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "derived_from_execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.external_object_mentions": { + "name": "external_object_mentions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_record_id": { + "name": "source_record_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "document_key": { + "name": "document_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "property_key": { + "name": "property_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "matched_text_redacted": { + "name": "matched_text_redacted", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sanitized_display_url": { + "name": "sanitized_display_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "canonical_identity_hash": { + "name": "canonical_identity_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "canonical_identity": { + "name": "canonical_identity", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "object_id": { + "name": "object_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider_key": { + "name": "provider_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "detector_key": { + "name": "detector_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "object_type": { + "name": "object_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "confidence": { + "name": "confidence", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'exact'" + }, + "created_by_plugin_id": { + "name": "created_by_plugin_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": { + "external_object_mentions_company_source_issue_idx": { + "name": "external_object_mentions_company_source_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_object_mentions_company_object_idx": { + "name": "external_object_mentions_company_object_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_object_mentions_company_provider_idx": { + "name": "external_object_mentions_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_object_mentions_company_source_record_uq": { + "name": "external_object_mentions_company_source_record_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_record_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "property_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "canonical_identity_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"external_object_mentions\".\"source_record_id\" is not null and \"external_object_mentions\".\"canonical_identity_hash\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_object_mentions_company_source_null_record_uq": { + "name": "external_object_mentions_company_source_null_record_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "property_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "canonical_identity_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"external_object_mentions\".\"source_record_id\" is null and \"external_object_mentions\".\"canonical_identity_hash\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "external_object_mentions_company_id_companies_id_fk": { + "name": "external_object_mentions_company_id_companies_id_fk", + "tableFrom": "external_object_mentions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "external_object_mentions_source_issue_id_issues_id_fk": { + "name": "external_object_mentions_source_issue_id_issues_id_fk", + "tableFrom": "external_object_mentions", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "external_object_mentions_object_id_external_objects_id_fk": { + "name": "external_object_mentions_object_id_external_objects_id_fk", + "tableFrom": "external_object_mentions", + "tableTo": "external_objects", + "columnsFrom": [ + "object_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "external_object_mentions_created_by_plugin_id_plugins_id_fk": { + "name": "external_object_mentions_created_by_plugin_id_plugins_id_fk", + "tableFrom": "external_object_mentions", + "tableTo": "plugins", + "columnsFrom": [ + "created_by_plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.external_objects": { + "name": "external_objects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider_key": { + "name": "provider_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "object_type": { + "name": "object_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sanitized_canonical_url": { + "name": "sanitized_canonical_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "canonical_identity_hash": { + "name": "canonical_identity_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "display_key": { + "name": "display_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon_key": { + "name": "icon_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "display_title": { + "name": "display_title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_key": { + "name": "status_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_label": { + "name": "status_label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_icon_key": { + "name": "status_icon_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_category": { + "name": "status_category", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "status_tone": { + "name": "status_tone", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'neutral'" + }, + "liveness": { + "name": "liveness", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "is_terminal": { + "name": "is_terminal", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "remote_version": { + "name": "remote_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "etag": { + "name": "etag", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_resolved_at": { + "name": "last_resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_changed_at": { + "name": "last_changed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error_at": { + "name": "last_error_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_refresh_at": { + "name": "next_refresh_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_started_at": { + "name": "refresh_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_error_code": { + "name": "last_error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error_message": { + "name": "last_error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "external_objects_company_provider_object_idx": { + "name": "external_objects_company_provider_object_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_objects_company_provider_status_idx": { + "name": "external_objects_company_provider_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status_category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_objects_company_refresh_idx": { + "name": "external_objects_company_refresh_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_refresh_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_objects_company_external_id_uq": { + "name": "external_objects_company_external_id_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_objects_company_identity_uq": { + "name": "external_objects_company_identity_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "canonical_identity_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "external_objects_company_id_companies_id_fk": { + "name": "external_objects_company_id_companies_id_fk", + "tableFrom": "external_objects", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "external_objects_plugin_id_plugins_id_fk": { + "name": "external_objects_plugin_id_plugins_id_fk", + "tableFrom": "external_objects", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.feedback_exports": { + "name": "feedback_exports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "feedback_vote_id": { + "name": "feedback_vote_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vote": { + "name": "vote", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_only'" + }, + "destination": { + "name": "destination", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "export_id": { + "name": "export_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "consent_version": { + "name": "consent_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "schema_version": { + "name": "schema_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip-feedback-envelope-v2'" + }, + "bundle_version": { + "name": "bundle_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip-feedback-bundle-v2'" + }, + "payload_version": { + "name": "payload_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip-feedback-v1'" + }, + "payload_digest": { + "name": "payload_digest", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload_snapshot": { + "name": "payload_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "target_summary": { + "name": "target_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "redaction_summary": { + "name": "redaction_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempted_at": { + "name": "last_attempted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "exported_at": { + "name": "exported_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "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": { + "feedback_exports_feedback_vote_idx": { + "name": "feedback_exports_feedback_vote_idx", + "columns": [ + { + "expression": "feedback_vote_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_created_idx": { + "name": "feedback_exports_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_status_idx": { + "name": "feedback_exports_company_status_idx", + "columns": [ + { + "expression": "company_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": {} + }, + "feedback_exports_company_issue_idx": { + "name": "feedback_exports_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_project_idx": { + "name": "feedback_exports_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_author_idx": { + "name": "feedback_exports_company_author_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "feedback_exports_company_id_companies_id_fk": { + "name": "feedback_exports_company_id_companies_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "feedback_exports_feedback_vote_id_feedback_votes_id_fk": { + "name": "feedback_exports_feedback_vote_id_feedback_votes_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "feedback_votes", + "columnsFrom": [ + "feedback_vote_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "feedback_exports_issue_id_issues_id_fk": { + "name": "feedback_exports_issue_id_issues_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "feedback_exports_project_id_projects_id_fk": { + "name": "feedback_exports_project_id_projects_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.feedback_votes": { + "name": "feedback_votes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "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 + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vote": { + "name": "vote", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shared_with_labs": { + "name": "shared_with_labs", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "shared_at": { + "name": "shared_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "consent_version": { + "name": "consent_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redaction_summary": { + "name": "redaction_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "feedback_votes_company_issue_idx": { + "name": "feedback_votes_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_votes_issue_target_idx": { + "name": "feedback_votes_issue_target_idx", + "columns": [ + { + "expression": "issue_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": {} + }, + "feedback_votes_author_idx": { + "name": "feedback_votes_author_idx", + "columns": [ + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_votes_company_target_author_idx": { + "name": "feedback_votes_company_target_author_idx", + "columns": [ + { + "expression": "company_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" + }, + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "feedback_votes_company_id_companies_id_fk": { + "name": "feedback_votes_company_id_companies_id_fk", + "tableFrom": "feedback_votes", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "feedback_votes_issue_id_issues_id_fk": { + "name": "feedback_votes_issue_id_issues_id_fk", + "tableFrom": "feedback_votes", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.finance_events": { + "name": "finance_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cost_event_id": { + "name": "cost_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "event_kind": { + "name": "event_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'debit'" + }, + "biller": { + "name": "biller", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_adapter_type": { + "name": "execution_adapter_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pricing_tier": { + "name": "pricing_tier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "amount_cents": { + "name": "amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "estimated": { + "name": "estimated", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "external_invoice_id": { + "name": "external_invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata_json": { + "name": "metadata_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "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": { + "finance_events_company_occurred_idx": { + "name": "finance_events_company_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_biller_occurred_idx": { + "name": "finance_events_company_biller_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "biller", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_kind_occurred_idx": { + "name": "finance_events_company_kind_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "event_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_direction_occurred_idx": { + "name": "finance_events_company_direction_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "direction", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_heartbeat_run_idx": { + "name": "finance_events_company_heartbeat_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_cost_event_idx": { + "name": "finance_events_company_cost_event_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "finance_events_company_id_companies_id_fk": { + "name": "finance_events_company_id_companies_id_fk", + "tableFrom": "finance_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_agent_id_agents_id_fk": { + "name": "finance_events_agent_id_agents_id_fk", + "tableFrom": "finance_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_issue_id_issues_id_fk": { + "name": "finance_events_issue_id_issues_id_fk", + "tableFrom": "finance_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "finance_events_project_id_projects_id_fk": { + "name": "finance_events_project_id_projects_id_fk", + "tableFrom": "finance_events", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_goal_id_goals_id_fk": { + "name": "finance_events_goal_id_goals_id_fk", + "tableFrom": "finance_events", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "finance_events_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "finance_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_cost_event_id_cost_events_id_fk": { + "name": "finance_events_cost_event_id_cost_events_id_fk", + "tableFrom": "finance_events", + "tableTo": "cost_events", + "columnsFrom": [ + "cost_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.folders": { + "name": "folders", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "system_key": { + "name": "system_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "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": { + "folders_company_kind_position_idx": { + "name": "folders_company_kind_position_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "folders_company_kind_root_slug_uq": { + "name": "folders_company_kind_root_slug_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"folders\".\"parent_id\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "folders_company_kind_parent_slug_uq": { + "name": "folders_company_kind_parent_slug_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"folders\".\"parent_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "folders_company_kind_system_key_uq": { + "name": "folders_company_kind_system_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "system_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"folders\".\"system_key\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "folders_company_kind_parent_position_idx": { + "name": "folders_company_kind_parent_position_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "folders_company_id_companies_id_fk": { + "name": "folders_company_id_companies_id_fk", + "tableFrom": "folders", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "folders_parent_id_folders_id_fk": { + "name": "folders_parent_id_folders_id_fk", + "tableFrom": "folders", + "tableTo": "folders", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.goals": { + "name": "goals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'task'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'planned'" + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_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": { + "goals_company_idx": { + "name": "goals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "goals_company_id_companies_id_fk": { + "name": "goals_company_id_companies_id_fk", + "tableFrom": "goals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "goals_parent_id_goals_id_fk": { + "name": "goals_parent_id_goals_id_fk", + "tableFrom": "goals", + "tableTo": "goals", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "goals_owner_agent_id_agents_id_fk": { + "name": "goals_owner_agent_id_agents_id_fk", + "tableFrom": "goals", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.heartbeat_run_events": { + "name": "heartbeat_run_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "seq": { + "name": "seq", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stream": { + "name": "stream", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "source_instance_id": { + "name": "source_instance_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_event_id": { + "name": "source_event_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_seq": { + "name": "source_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "source_payload_sha256": { + "name": "source_payload_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "protocol_schema_version": { + "name": "protocol_schema_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "heartbeat_run_events_run_seq_uq": { + "name": "heartbeat_run_events_run_seq_uq", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "seq", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_run_source_event_uq": { + "name": "heartbeat_run_events_run_source_event_uq", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"heartbeat_run_events\".\"source_event_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_run_source_seq_uq": { + "name": "heartbeat_run_events_run_source_seq_uq", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_seq", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"heartbeat_run_events\".\"source_instance_id\" is not null and \"heartbeat_run_events\".\"source_seq\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_company_run_idx": { + "name": "heartbeat_run_events_company_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_company_created_idx": { + "name": "heartbeat_run_events_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "heartbeat_run_events_company_id_companies_id_fk": { + "name": "heartbeat_run_events_company_id_companies_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_run_events_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_run_events_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_run_events_agent_id_agents_id_fk": { + "name": "heartbeat_run_events_agent_id_agents_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.heartbeat_run_watchdog_decisions": { + "name": "heartbeat_run_watchdog_decisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "evaluation_issue_id": { + "name": "evaluation_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "decision": { + "name": "decision", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "snoozed_until": { + "name": "snoozed_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "heartbeat_run_watchdog_decisions_company_run_created_idx": { + "name": "heartbeat_run_watchdog_decisions_company_run_created_idx", + "columns": [ + { + "expression": "company_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": {} + }, + "heartbeat_run_watchdog_decisions_company_run_snooze_idx": { + "name": "heartbeat_run_watchdog_decisions_company_run_snooze_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "snoozed_until", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "heartbeat_run_watchdog_decisions_company_id_companies_id_fk": { + "name": "heartbeat_run_watchdog_decisions_company_id_companies_id_fk", + "tableFrom": "heartbeat_run_watchdog_decisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_run_watchdog_decisions_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_run_watchdog_decisions_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_run_watchdog_decisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "heartbeat_run_watchdog_decisions_evaluation_issue_id_issues_id_fk": { + "name": "heartbeat_run_watchdog_decisions_evaluation_issue_id_issues_id_fk", + "tableFrom": "heartbeat_run_watchdog_decisions", + "tableTo": "issues", + "columnsFrom": [ + "evaluation_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "heartbeat_run_watchdog_decisions_created_by_agent_id_agents_id_fk": { + "name": "heartbeat_run_watchdog_decisions_created_by_agent_id_agents_id_fk", + "tableFrom": "heartbeat_run_watchdog_decisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "heartbeat_run_watchdog_decisions_created_by_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_run_watchdog_decisions_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_run_watchdog_decisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.heartbeat_runs": { + "name": "heartbeat_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "invocation_source": { + "name": "invocation_source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'on_demand'" + }, + "trigger_detail": { + "name": "trigger_detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "active_identity_context_id": { + "name": "active_identity_context_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "execution_control_deadline_at": { + "name": "execution_control_deadline_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "execution_status_delivery_id": { + "name": "execution_status_delivery_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "wakeup_request_id": { + "name": "wakeup_request_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "exit_code": { + "name": "exit_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "signal": { + "name": "signal", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "usage_json": { + "name": "usage_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "result_json": { + "name": "result_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "runtime_mode": { + "name": "runtime_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'legacy'" + }, + "runtime_mode_resolver_version": { + "name": "runtime_mode_resolver_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "runtime_mode_reason": { + "name": "runtime_mode_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "runtime_mode_resolved_at": { + "name": "runtime_mode_resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "runner_profile_json": { + "name": "runner_profile_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "runner_instance_id": { + "name": "runner_instance_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "native_session_id": { + "name": "native_session_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "native_issue_id": { + "name": "native_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "driver_kind": { + "name": "driver_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "driver_version": { + "name": "driver_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completion_contract_id": { + "name": "completion_contract_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "completion_contract_sha256": { + "name": "completion_contract_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_event_seq": { + "name": "next_event_seq", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "native_phase": { + "name": "native_phase", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "native_phase_updated_at": { + "name": "native_phase_updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "session_id_before": { + "name": "session_id_before", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_id_after": { + "name": "session_id_after", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_store": { + "name": "log_store", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_ref": { + "name": "log_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_bytes": { + "name": "log_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "log_sha256": { + "name": "log_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_compressed": { + "name": "log_compressed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "stdout_excerpt": { + "name": "stdout_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stderr_excerpt": { + "name": "stderr_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_run_id": { + "name": "external_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "controller_boot_id": { + "name": "controller_boot_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "controller_lease_expires_at": { + "name": "controller_lease_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "execution_stage": { + "name": "execution_stage", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "process_pid": { + "name": "process_pid", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "process_group_id": { + "name": "process_group_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "process_started_at": { + "name": "process_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_output_at": { + "name": "last_output_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_output_seq": { + "name": "last_output_seq", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_output_stream": { + "name": "last_output_stream", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_output_bytes": { + "name": "last_output_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "retry_of_run_id": { + "name": "retry_of_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "process_loss_retry_count": { + "name": "process_loss_retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "scheduled_retry_at": { + "name": "scheduled_retry_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scheduled_retry_attempt": { + "name": "scheduled_retry_attempt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "scheduled_retry_reason": { + "name": "scheduled_retry_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_comment_status": { + "name": "issue_comment_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'not_applicable'" + }, + "issue_comment_satisfied_by_comment_id": { + "name": "issue_comment_satisfied_by_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_comment_retry_queued_at": { + "name": "issue_comment_retry_queued_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "liveness_state": { + "name": "liveness_state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "liveness_reason": { + "name": "liveness_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "continuation_attempt": { + "name": "continuation_attempt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_useful_action_at": { + "name": "last_useful_action_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_action": { + "name": "next_action", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "context_snapshot": { + "name": "context_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "heartbeat_runs_execution_status_delivery_idx": { + "name": "heartbeat_runs_execution_status_delivery_idx", + "columns": [ + { + "expression": "execution_status_delivery_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"heartbeat_runs\".\"execution_status_delivery_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_execution_control_deadline_idx": { + "name": "heartbeat_runs_execution_control_deadline_idx", + "columns": [ + { + "expression": "execution_control_deadline_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"heartbeat_runs\".\"execution_control_deadline_at\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_native_replacement_predecessor_uq": { + "name": "heartbeat_runs_native_replacement_predecessor_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "retry_of_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"heartbeat_runs\".\"scheduled_retry_reason\" = 'native_safe_replacement'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_agent_started_idx": { + "name": "heartbeat_runs_company_agent_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_responsible_user_idx": { + "name": "heartbeat_runs_company_responsible_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "responsible_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_liveness_idx": { + "name": "heartbeat_runs_company_liveness_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "liveness_state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_status_last_output_idx": { + "name": "heartbeat_runs_company_status_last_output_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_output_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_status_process_started_idx": { + "name": "heartbeat_runs_company_status_process_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "process_started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_created_at_desc_idx": { + "name": "heartbeat_runs_company_created_at_desc_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_ctx_issue_created_idx": { + "name": "heartbeat_runs_company_ctx_issue_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "(\"context_snapshot\" ->> 'issueId')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_ctx_task_created_idx": { + "name": "heartbeat_runs_company_ctx_task_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "(\"context_snapshot\" ->> 'taskId')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_ctx_taskkey_created_idx": { + "name": "heartbeat_runs_company_ctx_taskkey_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "(\"context_snapshot\" ->> 'taskKey')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "heartbeat_runs_company_id_companies_id_fk": { + "name": "heartbeat_runs_company_id_companies_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_agent_id_agents_id_fk": { + "name": "heartbeat_runs_agent_id_agents_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_wakeup_request_id_agent_wakeup_requests_id_fk": { + "name": "heartbeat_runs_wakeup_request_id_agent_wakeup_requests_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "agent_wakeup_requests", + "columnsFrom": [ + "wakeup_request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_retry_of_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_runs_retry_of_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "retry_of_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "heartbeat_runs_company_native_issue_id_uq": { + "name": "heartbeat_runs_company_native_issue_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "native_issue_id", + "id" + ] + }, + "heartbeat_runs_company_native_issue_contract_id_uq": { + "name": "heartbeat_runs_company_native_issue_contract_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "native_issue_id", + "id", + "completion_contract_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.inbox_dismissals": { + "name": "inbox_dismissals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "item_key": { + "name": "item_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'dismiss'" + }, + "dismissed_at": { + "name": "dismissed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "snoozed_until": { + "name": "snoozed_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "inbox_dismissals_company_user_idx": { + "name": "inbox_dismissals_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_dismissals_company_item_idx": { + "name": "inbox_dismissals_company_item_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "item_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_dismissals_company_user_item_idx": { + "name": "inbox_dismissals_company_user_item_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "item_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "inbox_dismissals_company_id_companies_id_fk": { + "name": "inbox_dismissals_company_id_companies_id_fk", + "tableFrom": "inbox_dismissals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.connection_grant_delegations": { + "name": "connection_grant_delegations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "connection_grant_delegations_company_agent_idx": { + "name": "connection_grant_delegations_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_grant_delegations_grant_agent_uq": { + "name": "connection_grant_delegations_grant_agent_uq", + "columns": [ + { + "expression": "grant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "connection_grant_delegations_company_id_companies_id_fk": { + "name": "connection_grant_delegations_company_id_companies_id_fk", + "tableFrom": "connection_grant_delegations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_grant_delegations_agent_id_agents_id_fk": { + "name": "connection_grant_delegations_agent_id_agents_id_fk", + "tableFrom": "connection_grant_delegations", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_grant_delegations_company_grant_fk": { + "name": "connection_grant_delegations_company_grant_fk", + "tableFrom": "connection_grant_delegations", + "tableTo": "connection_grants", + "columnsFrom": [ + "company_id", + "grant_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.connection_grant_members": { + "name": "connection_grant_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "subject_type": { + "name": "subject_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_id": { + "name": "subject_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "connection_grant_members_company_subject_idx": { + "name": "connection_grant_members_company_subject_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_grant_members_grant_subject_uq": { + "name": "connection_grant_members_grant_subject_uq", + "columns": [ + { + "expression": "grant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "connection_grant_members_company_id_companies_id_fk": { + "name": "connection_grant_members_company_id_companies_id_fk", + "tableFrom": "connection_grant_members", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_grant_members_company_grant_fk": { + "name": "connection_grant_members_company_grant_fk", + "tableFrom": "connection_grant_members", + "tableTo": "connection_grants", + "columnsFrom": [ + "company_id", + "grant_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "connection_grant_members_subject_type_check": { + "name": "connection_grant_members_subject_type_check", + "value": "\"connection_grant_members\".\"subject_type\" in ('user')" + } + }, + "isRLSEnabled": false + }, + "public.connection_grants": { + "name": "connection_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_user_id": { + "name": "subject_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subject_agent_id": { + "name": "subject_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider_tenant": { + "name": "provider_tenant", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "credential_secret_refs": { + "name": "credential_secret_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "external_credential": { + "name": "external_credential", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_by_agent_id": { + "name": "revoked_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "revoked_by_user_id": { + "name": "revoked_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_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": { + "connection_grants_company_connection_idx": { + "name": "connection_grants_company_connection_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_grants_subject_user_idx": { + "name": "connection_grants_subject_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_grants_subject_agent_idx": { + "name": "connection_grants_subject_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_grants_user_uq": { + "name": "connection_grants_user_uq", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_grants_agent_uq": { + "name": "connection_grants_agent_uq", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_grants_default_uq": { + "name": "connection_grants_default_uq", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"connection_grants\".\"is_default\" = true and \"connection_grants\".\"kind\" = 'organization'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "connection_grants_company_id_companies_id_fk": { + "name": "connection_grants_company_id_companies_id_fk", + "tableFrom": "connection_grants", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_grants_subject_agent_id_agents_id_fk": { + "name": "connection_grants_subject_agent_id_agents_id_fk", + "tableFrom": "connection_grants", + "tableTo": "agents", + "columnsFrom": [ + "subject_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_grants_created_by_agent_id_agents_id_fk": { + "name": "connection_grants_created_by_agent_id_agents_id_fk", + "tableFrom": "connection_grants", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "connection_grants_revoked_by_agent_id_agents_id_fk": { + "name": "connection_grants_revoked_by_agent_id_agents_id_fk", + "tableFrom": "connection_grants", + "tableTo": "agents", + "columnsFrom": [ + "revoked_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "connection_grants_company_connection_fk": { + "name": "connection_grants_company_connection_fk", + "tableFrom": "connection_grants", + "tableTo": "tool_connections", + "columnsFrom": [ + "company_id", + "connection_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "connection_grants_company_id_uq": { + "name": "connection_grants_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "connection_grants_kind_check": { + "name": "connection_grants_kind_check", + "value": "\"connection_grants\".\"kind\" in ('organization', 'user', 'agent')" + }, + "connection_grants_status_check": { + "name": "connection_grants_status_check", + "value": "\"connection_grants\".\"status\" in ('active', 'revoked', 'expired', 'needs_reauthorization')" + }, + "connection_grants_credential_source_one_of_check": { + "name": "connection_grants_credential_source_one_of_check", + "value": "\"connection_grants\".\"external_credential\" is null or jsonb_array_length(\"connection_grants\".\"credential_secret_refs\") = 0" + }, + "connection_grants_subject_check": { + "name": "connection_grants_subject_check", + "value": "(\"connection_grants\".\"kind\" = 'user' and \"connection_grants\".\"subject_user_id\" is not null and \"connection_grants\".\"subject_agent_id\" is null) or (\"connection_grants\".\"kind\" = 'agent' and \"connection_grants\".\"subject_agent_id\" is not null and \"connection_grants\".\"subject_user_id\" is null) or (\"connection_grants\".\"kind\" = 'organization' and \"connection_grants\".\"subject_user_id\" is null and \"connection_grants\".\"subject_agent_id\" is null)" + }, + "connection_grants_default_check": { + "name": "connection_grants_default_check", + "value": "\"connection_grants\".\"is_default\" = false or \"connection_grants\".\"kind\" = 'organization'" + } + }, + "isRLSEnabled": false + }, + "public.connection_token_issuances": { + "name": "connection_token_issuances", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requested_scope": { + "name": "requested_scope", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "issued_scope": { + "name": "issued_scope", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "ttl_seconds": { + "name": "ttl_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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": { + "connection_token_issuances_company_created_idx": { + "name": "connection_token_issuances_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_token_issuances_connection_created_idx": { + "name": "connection_token_issuances_connection_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_token_issuances_agent_connection_idx": { + "name": "connection_token_issuances_agent_connection_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_token_issuances_run_idx": { + "name": "connection_token_issuances_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "connection_token_issuances_company_id_companies_id_fk": { + "name": "connection_token_issuances_company_id_companies_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_token_issuances_application_id_tool_applications_id_fk": { + "name": "connection_token_issuances_application_id_tool_applications_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "connection_token_issuances_connection_id_tool_connections_id_fk": { + "name": "connection_token_issuances_connection_id_tool_connections_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_token_issuances_agent_id_agents_id_fk": { + "name": "connection_token_issuances_agent_id_agents_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_token_issuances_run_id_heartbeat_runs_id_fk": { + "name": "connection_token_issuances_run_id_heartbeat_runs_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "connection_token_issuances_issue_id_issues_id_fk": { + "name": "connection_token_issuances_issue_id_issues_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "connection_token_issuances_project_id_projects_id_fk": { + "name": "connection_token_issuances_project_id_projects_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.instance_settings": { + "name": "instance_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "singleton_key": { + "name": "singleton_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "default_environment_id": { + "name": "default_environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "general": { + "name": "general", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "experimental": { + "name": "experimental", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "instance_settings_singleton_key_idx": { + "name": "instance_settings_singleton_key_idx", + "columns": [ + { + "expression": "singleton_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "instance_settings_default_environment_id_environments_id_fk": { + "name": "instance_settings_default_environment_id_environments_id_fk", + "tableFrom": "instance_settings", + "tableTo": "environments", + "columnsFrom": [ + "default_environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.instance_user_roles": { + "name": "instance_user_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'instance_admin'" + }, + "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": { + "instance_user_roles_user_role_unique_idx": { + "name": "instance_user_roles_user_role_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "instance_user_roles_role_idx": { + "name": "instance_user_roles_role_idx", + "columns": [ + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invites": { + "name": "invites", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "invite_type": { + "name": "invite_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company_join'" + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "allowed_join_types": { + "name": "allowed_join_types", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'both'" + }, + "defaults_payload": { + "name": "defaults_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "invited_by_user_id": { + "name": "invited_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "invites_token_hash_unique_idx": { + "name": "invites_token_hash_unique_idx", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invites_company_invite_state_idx": { + "name": "invites_company_invite_state_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "invite_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revoked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invites_company_id_companies_id_fk": { + "name": "invites_company_id_companies_id_fk", + "tableFrom": "invites", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_approvals": { + "name": "issue_approvals", + "schema": "", + "columns": { + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "linked_by_agent_id": { + "name": "linked_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "linked_by_user_id": { + "name": "linked_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_approvals_issue_idx": { + "name": "issue_approvals_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_approvals_approval_idx": { + "name": "issue_approvals_approval_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_approvals_company_idx": { + "name": "issue_approvals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_approvals_company_id_companies_id_fk": { + "name": "issue_approvals_company_id_companies_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_approvals_issue_id_issues_id_fk": { + "name": "issue_approvals_issue_id_issues_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_approvals_approval_id_approvals_id_fk": { + "name": "issue_approvals_approval_id_approvals_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_approvals_linked_by_agent_id_agents_id_fk": { + "name": "issue_approvals_linked_by_agent_id_agents_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "agents", + "columnsFrom": [ + "linked_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "issue_approvals_pk": { + "name": "issue_approvals_pk", + "columns": [ + "issue_id", + "approval_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_attachments": { + "name": "issue_attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_comment_id": { + "name": "issue_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "originating_run_id": { + "name": "originating_run_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": { + "issue_attachments_company_issue_idx": { + "name": "issue_attachments_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_attachments_issue_comment_idx": { + "name": "issue_attachments_issue_comment_idx", + "columns": [ + { + "expression": "issue_comment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_attachments_originating_run_idx": { + "name": "issue_attachments_originating_run_idx", + "columns": [ + { + "expression": "originating_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_attachments_asset_uq": { + "name": "issue_attachments_asset_uq", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_attachments_company_id_companies_id_fk": { + "name": "issue_attachments_company_id_companies_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_attachments_issue_id_issues_id_fk": { + "name": "issue_attachments_issue_id_issues_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_attachments_asset_id_assets_id_fk": { + "name": "issue_attachments_asset_id_assets_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_attachments_issue_comment_id_issue_comments_id_fk": { + "name": "issue_attachments_issue_comment_id_issue_comments_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "issue_comments", + "columnsFrom": [ + "issue_comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_attachments_originating_run_id_heartbeat_runs_id_fk": { + "name": "issue_attachments_originating_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "originating_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_comments": { + "name": "issue_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "on_behalf_of_user_id": { + "name": "on_behalf_of_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "author_type": { + "name": "author_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "derived_author_agent_id": { + "name": "derived_author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "derived_created_by_run_id": { + "name": "derived_created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "derived_author_source": { + "name": "derived_author_source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_request_id": { + "name": "client_request_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "conversation_session_generation": { + "name": "conversation_session_generation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "presentation": { + "name": "presentation", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "deleted_by_type": { + "name": "deleted_by_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_by_agent_id": { + "name": "deleted_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "deleted_by_user_id": { + "name": "deleted_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_by_run_id": { + "name": "deleted_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_trust": { + "name": "source_trust", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_comments_issue_idx": { + "name": "issue_comments_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_idx": { + "name": "issue_comments_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_issue_created_at_idx": { + "name": "issue_comments_company_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_author_issue_created_at_idx": { + "name": "issue_comments_company_author_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_body_search_idx": { + "name": "issue_comments_body_search_idx", + "columns": [ + { + "expression": "body", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "issue_comments_company_id_companies_id_fk": { + "name": "issue_comments_company_id_companies_id_fk", + "tableFrom": "issue_comments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_comments_issue_id_issues_id_fk": { + "name": "issue_comments_issue_id_issues_id_fk", + "tableFrom": "issue_comments", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_comments_author_agent_id_agents_id_fk": { + "name": "issue_comments_author_agent_id_agents_id_fk", + "tableFrom": "issue_comments", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_comments_on_behalf_of_user_id_user_id_fk": { + "name": "issue_comments_on_behalf_of_user_id_user_id_fk", + "tableFrom": "issue_comments", + "tableTo": "user", + "columnsFrom": [ + "on_behalf_of_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_comments_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_comments_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_comments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_comments_derived_author_agent_id_agents_id_fk": { + "name": "issue_comments_derived_author_agent_id_agents_id_fk", + "tableFrom": "issue_comments", + "tableTo": "agents", + "columnsFrom": [ + "derived_author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_comments_derived_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_comments_derived_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_comments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "derived_created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_comments_deleted_by_agent_id_agents_id_fk": { + "name": "issue_comments_deleted_by_agent_id_agents_id_fk", + "tableFrom": "issue_comments", + "tableTo": "agents", + "columnsFrom": [ + "deleted_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_comments_deleted_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_comments_deleted_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_comments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "deleted_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "issue_comments_client_request_uq": { + "name": "issue_comments_client_request_uq", + "nullsNotDistinct": false, + "columns": [ + "issue_id", + "author_user_id", + "client_request_id" + ] + }, + "issue_comments_company_id_uq": { + "name": "issue_comments_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_create_idempotency_keys": { + "name": "issue_create_idempotency_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_create_idempotency_keys_company_key_uq": { + "name": "issue_create_idempotency_keys_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_create_idempotency_keys_issue_idx": { + "name": "issue_create_idempotency_keys_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_create_idempotency_keys_company_created_at_idx": { + "name": "issue_create_idempotency_keys_company_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_create_idempotency_keys_company_id_companies_id_fk": { + "name": "issue_create_idempotency_keys_company_id_companies_id_fk", + "tableFrom": "issue_create_idempotency_keys", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_create_idempotency_keys_issue_id_issues_id_fk": { + "name": "issue_create_idempotency_keys_issue_id_issues_id_fk", + "tableFrom": "issue_create_idempotency_keys", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_documents": { + "name": "issue_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "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": { + "issue_documents_company_issue_key_uq": { + "name": "issue_documents_company_issue_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_documents_document_uq": { + "name": "issue_documents_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_documents_company_issue_updated_idx": { + "name": "issue_documents_company_issue_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_documents_company_id_companies_id_fk": { + "name": "issue_documents_company_id_companies_id_fk", + "tableFrom": "issue_documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_documents_issue_id_issues_id_fk": { + "name": "issue_documents_issue_id_issues_id_fk", + "tableFrom": "issue_documents", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_documents_document_id_documents_id_fk": { + "name": "issue_documents_document_id_documents_id_fk", + "tableFrom": "issue_documents", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_execution_decisions": { + "name": "issue_execution_decisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "stage_id": { + "name": "stage_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "stage_type": { + "name": "stage_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_agent_id": { + "name": "actor_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_run_id": { + "name": "created_by_run_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": { + "issue_execution_decisions_company_issue_idx": { + "name": "issue_execution_decisions_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_execution_decisions_stage_idx": { + "name": "issue_execution_decisions_stage_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_execution_decisions_company_id_companies_id_fk": { + "name": "issue_execution_decisions_company_id_companies_id_fk", + "tableFrom": "issue_execution_decisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_execution_decisions_issue_id_issues_id_fk": { + "name": "issue_execution_decisions_issue_id_issues_id_fk", + "tableFrom": "issue_execution_decisions", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_execution_decisions_actor_agent_id_agents_id_fk": { + "name": "issue_execution_decisions_actor_agent_id_agents_id_fk", + "tableFrom": "issue_execution_decisions", + "tableTo": "agents", + "columnsFrom": [ + "actor_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_execution_decisions_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_execution_decisions_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_execution_decisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_inbox_archives": { + "name": "issue_inbox_archives", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "archived_by_actor_type": { + "name": "archived_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'user'" + }, + "archived_by_agent_id": { + "name": "archived_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "archived_by_run_id": { + "name": "archived_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_inbox_archives_company_issue_idx": { + "name": "issue_inbox_archives_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_inbox_archives_company_user_idx": { + "name": "issue_inbox_archives_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_inbox_archives_company_issue_user_idx": { + "name": "issue_inbox_archives_company_issue_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_inbox_archives_company_id_companies_id_fk": { + "name": "issue_inbox_archives_company_id_companies_id_fk", + "tableFrom": "issue_inbox_archives", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_inbox_archives_issue_id_issues_id_fk": { + "name": "issue_inbox_archives_issue_id_issues_id_fk", + "tableFrom": "issue_inbox_archives", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_inbox_archives_archived_by_agent_id_agents_id_fk": { + "name": "issue_inbox_archives_archived_by_agent_id_agents_id_fk", + "tableFrom": "issue_inbox_archives", + "tableTo": "agents", + "columnsFrom": [ + "archived_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_inbox_archives_archived_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_inbox_archives_archived_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_inbox_archives", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "archived_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "issue_inbox_archives_archived_by_actor_type_check": { + "name": "issue_inbox_archives_archived_by_actor_type_check", + "value": "\"issue_inbox_archives\".\"archived_by_actor_type\" in ('user', 'agent')" + } + }, + "isRLSEnabled": false + }, + "public.issue_labels": { + "name": "issue_labels", + "schema": "", + "columns": { + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label_id": { + "name": "label_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_labels_issue_idx": { + "name": "issue_labels_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_labels_label_idx": { + "name": "issue_labels_label_idx", + "columns": [ + { + "expression": "label_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_labels_company_idx": { + "name": "issue_labels_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_labels_issue_id_issues_id_fk": { + "name": "issue_labels_issue_id_issues_id_fk", + "tableFrom": "issue_labels", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_labels_label_id_labels_id_fk": { + "name": "issue_labels_label_id_labels_id_fk", + "tableFrom": "issue_labels", + "tableTo": "labels", + "columnsFrom": [ + "label_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_labels_company_id_companies_id_fk": { + "name": "issue_labels_company_id_companies_id_fk", + "tableFrom": "issue_labels", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "issue_labels_pk": { + "name": "issue_labels_pk", + "columns": [ + "issue_id", + "label_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_plan_decompositions": { + "name": "issue_plan_decompositions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "accepted_plan_revision_id": { + "name": "accepted_plan_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "accepted_interaction_id": { + "name": "accepted_interaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'in_flight'" + }, + "request_fingerprint": { + "name": "request_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requested_child_count": { + "name": "requested_child_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "requested_children": { + "name": "requested_children", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "child_issue_ids": { + "name": "child_issue_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_run_id": { + "name": "owner_run_id", + "type": "uuid", + "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": { + "issue_plan_decompositions_company_source_status_idx": { + "name": "issue_plan_decompositions_company_source_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_plan_decompositions_active_owner_idx": { + "name": "issue_plan_decompositions_active_owner_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"issue_plan_decompositions\".\"status\" = 'in_flight'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_plan_decompositions_source_revision_uq": { + "name": "issue_plan_decompositions_source_revision_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "accepted_plan_revision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_plan_decompositions_company_id_companies_id_fk": { + "name": "issue_plan_decompositions_company_id_companies_id_fk", + "tableFrom": "issue_plan_decompositions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_plan_decompositions_source_issue_id_issues_id_fk": { + "name": "issue_plan_decompositions_source_issue_id_issues_id_fk", + "tableFrom": "issue_plan_decompositions", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_plan_decompositions_accepted_plan_revision_id_document_revisions_id_fk": { + "name": "issue_plan_decompositions_accepted_plan_revision_id_document_revisions_id_fk", + "tableFrom": "issue_plan_decompositions", + "tableTo": "document_revisions", + "columnsFrom": [ + "accepted_plan_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_plan_decompositions_accepted_interaction_id_issue_thread_interactions_id_fk": { + "name": "issue_plan_decompositions_accepted_interaction_id_issue_thread_interactions_id_fk", + "tableFrom": "issue_plan_decompositions", + "tableTo": "issue_thread_interactions", + "columnsFrom": [ + "accepted_interaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_plan_decompositions_owner_agent_id_agents_id_fk": { + "name": "issue_plan_decompositions_owner_agent_id_agents_id_fk", + "tableFrom": "issue_plan_decompositions", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_plan_decompositions_owner_run_id_heartbeat_runs_id_fk": { + "name": "issue_plan_decompositions_owner_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_plan_decompositions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "owner_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_question_response_deliveries": { + "name": "issue_question_response_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "interaction_id": { + "name": "interaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_run_id": { + "name": "source_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_run_id": { + "name": "target_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_turn_id": { + "name": "target_turn_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload_sha256": { + "name": "payload_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "delivery_mode": { + "name": "delivery_mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error_count": { + "name": "error_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "acknowledged_at": { + "name": "acknowledged_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error_code": { + "name": "last_error_code", + "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": { + "issue_question_response_deliveries_interaction_uq": { + "name": "issue_question_response_deliveries_interaction_uq", + "columns": [ + { + "expression": "interaction_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_question_response_deliveries_correlation_uq": { + "name": "issue_question_response_deliveries_correlation_uq", + "columns": [ + { + "expression": "correlation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_question_response_deliveries_pending_idx": { + "name": "issue_question_response_deliveries_pending_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_question_response_deliveries_company_issue_idx": { + "name": "issue_question_response_deliveries_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_question_response_deliveries_company_id_companies_id_fk": { + "name": "issue_question_response_deliveries_company_id_companies_id_fk", + "tableFrom": "issue_question_response_deliveries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_question_response_deliveries_issue_id_issues_id_fk": { + "name": "issue_question_response_deliveries_issue_id_issues_id_fk", + "tableFrom": "issue_question_response_deliveries", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_question_response_deliveries_interaction_id_issue_thread_interactions_id_fk": { + "name": "issue_question_response_deliveries_interaction_id_issue_thread_interactions_id_fk", + "tableFrom": "issue_question_response_deliveries", + "tableTo": "issue_thread_interactions", + "columnsFrom": [ + "interaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_question_response_deliveries_source_run_id_heartbeat_runs_id_fk": { + "name": "issue_question_response_deliveries_source_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_question_response_deliveries", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "source_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_question_response_deliveries_target_run_id_heartbeat_runs_id_fk": { + "name": "issue_question_response_deliveries_target_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_question_response_deliveries", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "target_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "issue_question_response_deliveries_status_check": { + "name": "issue_question_response_deliveries_status_check", + "value": "\"issue_question_response_deliveries\".\"status\" IN ('pending', 'delivering', 'delivered', 'fallback_queued', 'failed')" + }, + "issue_question_response_deliveries_mode_check": { + "name": "issue_question_response_deliveries_mode_check", + "value": "\"issue_question_response_deliveries\".\"delivery_mode\" IS NULL OR \"issue_question_response_deliveries\".\"delivery_mode\" IN ('steered', 'coalesced', 'wake_fallback')" + } + }, + "isRLSEnabled": false + }, + "public.issue_read_states": { + "name": "issue_read_states", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_read_at": { + "name": "last_read_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_read_states_company_issue_idx": { + "name": "issue_read_states_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_read_states_company_user_idx": { + "name": "issue_read_states_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_read_states_company_issue_user_idx": { + "name": "issue_read_states_company_issue_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_read_states_company_id_companies_id_fk": { + "name": "issue_read_states_company_id_companies_id_fk", + "tableFrom": "issue_read_states", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_read_states_issue_id_issues_id_fk": { + "name": "issue_read_states_issue_id_issues_id_fk", + "tableFrom": "issue_read_states", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_recovery_actions": { + "name": "issue_recovery_actions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "recovery_issue_id": { + "name": "recovery_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "owner_type": { + "name": "owner_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'agent'" + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previous_owner_agent_id": { + "name": "previous_owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "return_owner_agent_id": { + "name": "return_owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cause": { + "name": "cause", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fingerprint": { + "name": "fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "evidence": { + "name": "evidence", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "next_action": { + "name": "next_action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "wake_policy": { + "name": "wake_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "monitor_policy": { + "name": "monitor_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "max_attempts": { + "name": "max_attempts", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "timeout_at": { + "name": "timeout_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolution_note": { + "name": "resolution_note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_recovery_actions_company_source_status_idx": { + "name": "issue_recovery_actions_company_source_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_recovery_actions_company_owner_status_idx": { + "name": "issue_recovery_actions_company_owner_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_recovery_actions_company_recovery_issue_idx": { + "name": "issue_recovery_actions_company_recovery_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "recovery_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_recovery_actions_active_source_uq": { + "name": "issue_recovery_actions_active_source_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issue_recovery_actions\".\"status\" in ('active', 'escalated')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_recovery_actions_active_fingerprint_uq": { + "name": "issue_recovery_actions_active_fingerprint_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cause", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issue_recovery_actions\".\"status\" in ('active', 'escalated')", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_recovery_actions_company_id_companies_id_fk": { + "name": "issue_recovery_actions_company_id_companies_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_recovery_actions_source_issue_id_issues_id_fk": { + "name": "issue_recovery_actions_source_issue_id_issues_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_recovery_actions_recovery_issue_id_issues_id_fk": { + "name": "issue_recovery_actions_recovery_issue_id_issues_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "issues", + "columnsFrom": [ + "recovery_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_recovery_actions_owner_agent_id_agents_id_fk": { + "name": "issue_recovery_actions_owner_agent_id_agents_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_recovery_actions_previous_owner_agent_id_agents_id_fk": { + "name": "issue_recovery_actions_previous_owner_agent_id_agents_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "agents", + "columnsFrom": [ + "previous_owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_recovery_actions_return_owner_agent_id_agents_id_fk": { + "name": "issue_recovery_actions_return_owner_agent_id_agents_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "agents", + "columnsFrom": [ + "return_owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_reference_mentions": { + "name": "issue_reference_mentions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_issue_id": { + "name": "target_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_record_id": { + "name": "source_record_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "document_key": { + "name": "document_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "matched_text": { + "name": "matched_text", + "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": { + "issue_reference_mentions_company_source_issue_idx": { + "name": "issue_reference_mentions_company_source_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_reference_mentions_company_target_issue_idx": { + "name": "issue_reference_mentions_company_target_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_reference_mentions_company_issue_pair_idx": { + "name": "issue_reference_mentions_company_issue_pair_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_reference_mentions_company_source_mention_record_uq": { + "name": "issue_reference_mentions_company_source_mention_record_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_record_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issue_reference_mentions\".\"source_record_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_reference_mentions_company_source_mention_null_record_uq": { + "name": "issue_reference_mentions_company_source_mention_null_record_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issue_reference_mentions\".\"source_record_id\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_reference_mentions_company_id_companies_id_fk": { + "name": "issue_reference_mentions_company_id_companies_id_fk", + "tableFrom": "issue_reference_mentions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_reference_mentions_source_issue_id_issues_id_fk": { + "name": "issue_reference_mentions_source_issue_id_issues_id_fk", + "tableFrom": "issue_reference_mentions", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_reference_mentions_target_issue_id_issues_id_fk": { + "name": "issue_reference_mentions_target_issue_id_issues_id_fk", + "tableFrom": "issue_reference_mentions", + "tableTo": "issues", + "columnsFrom": [ + "target_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_relations": { + "name": "issue_relations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "related_issue_id": { + "name": "related_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_relations_company_issue_idx": { + "name": "issue_relations_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_relations_company_related_issue_idx": { + "name": "issue_relations_company_related_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "related_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_relations_company_type_idx": { + "name": "issue_relations_company_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_relations_company_edge_uq": { + "name": "issue_relations_company_edge_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "related_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_relations_company_id_companies_id_fk": { + "name": "issue_relations_company_id_companies_id_fk", + "tableFrom": "issue_relations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_relations_issue_id_issues_id_fk": { + "name": "issue_relations_issue_id_issues_id_fk", + "tableFrom": "issue_relations", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_relations_related_issue_id_issues_id_fk": { + "name": "issue_relations_related_issue_id_issues_id_fk", + "tableFrom": "issue_relations", + "tableTo": "issues", + "columnsFrom": [ + "related_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_relations_created_by_agent_id_agents_id_fk": { + "name": "issue_relations_created_by_agent_id_agents_id_fk", + "tableFrom": "issue_relations", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_thread_interactions": { + "name": "issue_thread_interactions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "continuation_policy": { + "name": "continuation_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'wake_assignee'" + }, + "requested_resolver_policy": { + "name": "requested_resolver_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'anyone'" + }, + "effective_resolver_policy": { + "name": "effective_resolver_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'anyone'" + }, + "resolver_policy_provenance": { + "name": "resolver_policy_provenance", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'inherited'" + }, + "effective_resolver_policy_source": { + "name": "effective_resolver_policy_source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'requested'" + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_comment_ids": { + "name": "origin_comment_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "source_comment_id": { + "name": "source_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_identity_context_id": { + "name": "source_identity_context_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_run_id": { + "name": "source_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "addressee_agent_id": { + "name": "addressee_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "addressee_user_id": { + "name": "addressee_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_by_agent_id": { + "name": "resolved_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_by_run_id": { + "name": "resolved_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_by_user_id": { + "name": "resolved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_thread_interactions_issue_idx": { + "name": "issue_thread_interactions_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_thread_interactions_company_issue_created_at_idx": { + "name": "issue_thread_interactions_company_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_thread_interactions_company_issue_status_idx": { + "name": "issue_thread_interactions_company_issue_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_thread_interactions_company_issue_idempotency_uq": { + "name": "issue_thread_interactions_company_issue_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issue_thread_interactions\".\"idempotency_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_thread_interactions_source_comment_idx": { + "name": "issue_thread_interactions_source_comment_idx", + "columns": [ + { + "expression": "source_comment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_thread_interactions_addressee_agent_idx": { + "name": "issue_thread_interactions_addressee_agent_idx", + "columns": [ + { + "expression": "addressee_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_thread_interactions_addressee_user_idx": { + "name": "issue_thread_interactions_addressee_user_idx", + "columns": [ + { + "expression": "addressee_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_thread_interactions_company_id_companies_id_fk": { + "name": "issue_thread_interactions_company_id_companies_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_thread_interactions_issue_id_issues_id_fk": { + "name": "issue_thread_interactions_issue_id_issues_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_thread_interactions_source_comment_id_issue_comments_id_fk": { + "name": "issue_thread_interactions_source_comment_id_issue_comments_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "issue_comments", + "columnsFrom": [ + "source_comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_thread_interactions_source_run_id_heartbeat_runs_id_fk": { + "name": "issue_thread_interactions_source_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "source_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_thread_interactions_created_by_agent_id_agents_id_fk": { + "name": "issue_thread_interactions_created_by_agent_id_agents_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_thread_interactions_addressee_agent_id_agents_id_fk": { + "name": "issue_thread_interactions_addressee_agent_id_agents_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "agents", + "columnsFrom": [ + "addressee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_thread_interactions_resolved_by_agent_id_agents_id_fk": { + "name": "issue_thread_interactions_resolved_by_agent_id_agents_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "agents", + "columnsFrom": [ + "resolved_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_thread_interactions_resolved_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_thread_interactions_resolved_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "resolved_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_tree_hold_members": { + "name": "issue_tree_hold_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "hold_id": { + "name": "hold_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "parent_issue_id": { + "name": "parent_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "depth": { + "name": "depth", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "issue_identifier": { + "name": "issue_identifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_title": { + "name": "issue_title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "issue_status": { + "name": "issue_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "assignee_agent_id": { + "name": "assignee_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "assignee_user_id": { + "name": "assignee_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "active_run_id": { + "name": "active_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "active_run_status": { + "name": "active_run_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "skipped": { + "name": "skipped", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "skip_reason": { + "name": "skip_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_tree_hold_members_hold_issue_uq": { + "name": "issue_tree_hold_members_hold_issue_uq", + "columns": [ + { + "expression": "hold_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_tree_hold_members_company_issue_idx": { + "name": "issue_tree_hold_members_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_tree_hold_members_hold_depth_idx": { + "name": "issue_tree_hold_members_hold_depth_idx", + "columns": [ + { + "expression": "hold_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "depth", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_tree_hold_members_company_id_companies_id_fk": { + "name": "issue_tree_hold_members_company_id_companies_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_tree_hold_members_hold_id_issue_tree_holds_id_fk": { + "name": "issue_tree_hold_members_hold_id_issue_tree_holds_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "issue_tree_holds", + "columnsFrom": [ + "hold_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_tree_hold_members_issue_id_issues_id_fk": { + "name": "issue_tree_hold_members_issue_id_issues_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_tree_hold_members_parent_issue_id_issues_id_fk": { + "name": "issue_tree_hold_members_parent_issue_id_issues_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "issues", + "columnsFrom": [ + "parent_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_tree_hold_members_assignee_agent_id_agents_id_fk": { + "name": "issue_tree_hold_members_assignee_agent_id_agents_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "agents", + "columnsFrom": [ + "assignee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_tree_hold_members_active_run_id_heartbeat_runs_id_fk": { + "name": "issue_tree_hold_members_active_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "active_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_tree_holds": { + "name": "issue_tree_holds", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "root_issue_id": { + "name": "root_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "release_policy": { + "name": "release_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_by_actor_type": { + "name": "created_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "released_at": { + "name": "released_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "released_by_actor_type": { + "name": "released_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "released_by_agent_id": { + "name": "released_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "released_by_user_id": { + "name": "released_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "released_by_run_id": { + "name": "released_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "release_reason": { + "name": "release_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "release_metadata": { + "name": "release_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_tree_holds_company_root_status_idx": { + "name": "issue_tree_holds_company_root_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "root_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_tree_holds_company_status_mode_idx": { + "name": "issue_tree_holds_company_status_mode_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "mode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_tree_holds_company_id_companies_id_fk": { + "name": "issue_tree_holds_company_id_companies_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_tree_holds_root_issue_id_issues_id_fk": { + "name": "issue_tree_holds_root_issue_id_issues_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "issues", + "columnsFrom": [ + "root_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_tree_holds_created_by_agent_id_agents_id_fk": { + "name": "issue_tree_holds_created_by_agent_id_agents_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_tree_holds_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_tree_holds_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_tree_holds_released_by_agent_id_agents_id_fk": { + "name": "issue_tree_holds_released_by_agent_id_agents_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "agents", + "columnsFrom": [ + "released_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_tree_holds_released_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_tree_holds_released_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "released_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_watchdogs": { + "name": "issue_watchdogs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "watchdog_agent_id": { + "name": "watchdog_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "instructions": { + "name": "instructions", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "watchdog_issue_id": { + "name": "watchdog_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_observed_fingerprint": { + "name": "last_observed_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_reviewed_fingerprint": { + "name": "last_reviewed_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_observed_stop_snapshot": { + "name": "last_observed_stop_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "last_reviewed_stop_snapshot": { + "name": "last_reviewed_stop_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "last_triggered_at": { + "name": "last_triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_completed_at": { + "name": "last_completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "trigger_count": { + "name": "trigger_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_run_id": { + "name": "updated_by_run_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": { + "issue_watchdogs_company_issue_uq": { + "name": "issue_watchdogs_company_issue_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_watchdogs_company_status_idx": { + "name": "issue_watchdogs_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_watchdogs_company_agent_idx": { + "name": "issue_watchdogs_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "watchdog_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_watchdogs_company_watchdog_issue_uq": { + "name": "issue_watchdogs_company_watchdog_issue_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "watchdog_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issue_watchdogs\".\"watchdog_issue_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_watchdogs_company_id_companies_id_fk": { + "name": "issue_watchdogs_company_id_companies_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_watchdogs_issue_id_issues_id_fk": { + "name": "issue_watchdogs_issue_id_issues_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_watchdogs_watchdog_agent_id_agents_id_fk": { + "name": "issue_watchdogs_watchdog_agent_id_agents_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "agents", + "columnsFrom": [ + "watchdog_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_watchdogs_watchdog_issue_id_issues_id_fk": { + "name": "issue_watchdogs_watchdog_issue_id_issues_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "issues", + "columnsFrom": [ + "watchdog_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_watchdogs_created_by_agent_id_agents_id_fk": { + "name": "issue_watchdogs_created_by_agent_id_agents_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_watchdogs_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_watchdogs_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_watchdogs_updated_by_agent_id_agents_id_fk": { + "name": "issue_watchdogs_updated_by_agent_id_agents_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_watchdogs_updated_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_watchdogs_updated_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "updated_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_work_products": { + "name": "issue_work_products", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "runtime_service_id": { + "name": "runtime_service_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "review_state": { + "name": "review_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "source_trust": { + "name": "source_trust", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_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": { + "issue_work_products_company_issue_type_idx": { + "name": "issue_work_products_company_issue_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_execution_workspace_type_idx": { + "name": "issue_work_products_company_execution_workspace_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_provider_external_id_idx": { + "name": "issue_work_products_company_provider_external_id_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_updated_idx": { + "name": "issue_work_products_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_work_products_company_id_companies_id_fk": { + "name": "issue_work_products_company_id_companies_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_work_products_project_id_projects_id_fk": { + "name": "issue_work_products_project_id_projects_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_issue_id_issues_id_fk": { + "name": "issue_work_products_issue_id_issues_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_work_products_execution_workspace_id_execution_workspaces_id_fk": { + "name": "issue_work_products_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_runtime_service_id_workspace_runtime_services_id_fk": { + "name": "issue_work_products_runtime_service_id_workspace_runtime_services_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "workspace_runtime_services", + "columnsFrom": [ + "runtime_service_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_work_products_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issues": { + "name": "issues", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "conversation_agent_id": { + "name": "conversation_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "conversation_user_id": { + "name": "conversation_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "conversation_state": { + "name": "conversation_state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "conversation_session_generation": { + "name": "conversation_session_generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "conversation_boundary_comment_id": { + "name": "conversation_boundary_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'backlog'" + }, + "status_version": { + "name": "status_version", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_status_decision_id": { + "name": "last_status_decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "work_mode": { + "name": "work_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'standard'" + }, + "harness_kind": { + "name": "harness_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "review_policy": { + "name": "review_policy", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_agent_id": { + "name": "assignee_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "assignee_user_id": { + "name": "assignee_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "checkout_run_id": { + "name": "checkout_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_run_id": { + "name": "execution_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_agent_name_key": { + "name": "execution_agent_name_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_locked_at": { + "name": "execution_locked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_number": { + "name": "issue_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_kind": { + "name": "origin_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "origin_id": { + "name": "origin_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_run_id": { + "name": "origin_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_identity_context_id": { + "name": "origin_identity_context_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "continuation_identity_context_id": { + "name": "continuation_identity_context_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "origin_fingerprint": { + "name": "origin_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "request_depth": { + "name": "request_depth", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_adapter_overrides": { + "name": "assignee_adapter_overrides", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "execution_policy": { + "name": "execution_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "execution_state": { + "name": "execution_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "monitor_next_check_at": { + "name": "monitor_next_check_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "monitor_wake_requested_at": { + "name": "monitor_wake_requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "monitor_last_triggered_at": { + "name": "monitor_last_triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "monitor_attempt_count": { + "name": "monitor_attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "monitor_notes": { + "name": "monitor_notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "monitor_scheduled_by": { + "name": "monitor_scheduled_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_preference": { + "name": "execution_workspace_preference", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_settings": { + "name": "execution_workspace_settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "source_trust": { + "name": "source_trust", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "unblock_descriptor": { + "name": "unblock_descriptor", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "blocked_transition_at": { + "name": "blocked_transition_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "blocked_owner_notified_at": { + "name": "blocked_owner_notified_at", + "type": "timestamp with time zone", + "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 + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "hidden_at": { + "name": "hidden_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": { + "issues_conversation_identity_idx": { + "name": "issues_conversation_identity_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "conversation_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "conversation_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_status_idx": { + "name": "issues_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_harness_kind_idx": { + "name": "issues_company_harness_kind_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "harness_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_assignee_status_idx": { + "name": "issues_company_assignee_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_assignee_user_status_idx": { + "name": "issues_company_assignee_user_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_responsible_user_idx": { + "name": "issues_company_responsible_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "responsible_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_parent_idx": { + "name": "issues_company_parent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_project_idx": { + "name": "issues_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_origin_idx": { + "name": "issues_company_origin_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_project_workspace_idx": { + "name": "issues_company_project_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_execution_workspace_idx": { + "name": "issues_company_execution_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_monitor_due_idx": { + "name": "issues_company_monitor_due_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "monitor_next_check_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_updated_idx": { + "name": "issues_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_created_idx": { + "name": "issues_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_open_normalized_title_created_idx": { + "name": "issues_open_normalized_title_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(regexp_replace(btrim(\"title\"), '\\s+', ' ', 'g'))", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"issues\".\"hidden_at\" is null and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_priority_idx": { + "name": "issues_company_priority_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_identifier_idx": { + "name": "issues_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_title_search_idx": { + "name": "issues_title_search_idx", + "columns": [ + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "issues_identifier_search_idx": { + "name": "issues_identifier_search_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "issues_description_search_idx": { + "name": "issues_description_search_idx", + "columns": [ + { + "expression": "description", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "issues_open_routine_execution_uq": { + "name": "issues_open_routine_execution_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'routine_execution'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"execution_run_id\" is not null\n and \"issues\".\"status\" in ('backlog', 'todo', 'in_progress', 'in_review', 'blocked')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_active_liveness_recovery_incident_uq": { + "name": "issues_active_liveness_recovery_incident_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'harness_liveness_escalation'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_active_liveness_recovery_leaf_uq": { + "name": "issues_active_liveness_recovery_leaf_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'harness_liveness_escalation'\n and \"issues\".\"origin_fingerprint\" <> 'default'\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_active_stale_run_evaluation_uq": { + "name": "issues_active_stale_run_evaluation_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'stale_active_run_evaluation'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_active_task_watchdog_uq": { + "name": "issues_active_task_watchdog_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'task_watchdog'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_active_productivity_review_uq": { + "name": "issues_active_productivity_review_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'issue_productivity_review'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_active_stranded_issue_recovery_uq": { + "name": "issues_active_stranded_issue_recovery_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'stranded_issue_recovery'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_onboarding_first_task_uq": { + "name": "issues_onboarding_first_task_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'onboarding_first_task'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issues_company_id_companies_id_fk": { + "name": "issues_company_id_companies_id_fk", + "tableFrom": "issues", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_conversation_agent_id_agents_id_fk": { + "name": "issues_conversation_agent_id_agents_id_fk", + "tableFrom": "issues", + "tableTo": "agents", + "columnsFrom": [ + "conversation_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_project_id_projects_id_fk": { + "name": "issues_project_id_projects_id_fk", + "tableFrom": "issues", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_project_workspace_id_project_workspaces_id_fk": { + "name": "issues_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "issues", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_goal_id_goals_id_fk": { + "name": "issues_goal_id_goals_id_fk", + "tableFrom": "issues", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_parent_id_issues_id_fk": { + "name": "issues_parent_id_issues_id_fk", + "tableFrom": "issues", + "tableTo": "issues", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_assignee_agent_id_agents_id_fk": { + "name": "issues_assignee_agent_id_agents_id_fk", + "tableFrom": "issues", + "tableTo": "agents", + "columnsFrom": [ + "assignee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_checkout_run_id_heartbeat_runs_id_fk": { + "name": "issues_checkout_run_id_heartbeat_runs_id_fk", + "tableFrom": "issues", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "checkout_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_execution_run_id_heartbeat_runs_id_fk": { + "name": "issues_execution_run_id_heartbeat_runs_id_fk", + "tableFrom": "issues", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "execution_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_created_by_agent_id_agents_id_fk": { + "name": "issues_created_by_agent_id_agents_id_fk", + "tableFrom": "issues", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_execution_workspace_id_execution_workspaces_id_fk": { + "name": "issues_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "issues", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "issues_company_id_uq": { + "name": "issues_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "issues_conversation_identity_check": { + "name": "issues_conversation_identity_check", + "value": "(\n \"issues\".\"conversation_agent_id\" is null and \"issues\".\"conversation_user_id\" is null and \"issues\".\"conversation_state\" is null\n ) or (\n \"issues\".\"conversation_agent_id\" is not null and \"issues\".\"conversation_user_id\" is not null\n and \"issues\".\"assignee_agent_id\" = \"issues\".\"conversation_agent_id\" and \"issues\".\"assignee_agent_id\" is not null\n and \"issues\".\"assignee_user_id\" is null and \"issues\".\"conversation_state\" is not null\n and \"issues\".\"conversation_state\" in ('active', 'waiting')\n and \"issues\".\"status\" not in ('done', 'cancelled')\n )" + } + }, + "isRLSEnabled": false + }, + "public.join_requests": { + "name": "join_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "invite_id": { + "name": "invite_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "request_type": { + "name": "request_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending_approval'" + }, + "request_ip": { + "name": "request_ip", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requesting_user_id": { + "name": "requesting_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_email_snapshot": { + "name": "request_email_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_name": { + "name": "agent_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_defaults_payload": { + "name": "agent_defaults_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "claim_secret_hash": { + "name": "claim_secret_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "claim_secret_expires_at": { + "name": "claim_secret_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "claim_secret_consumed_at": { + "name": "claim_secret_consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_agent_id": { + "name": "created_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approved_by_user_id": { + "name": "approved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "approved_at": { + "name": "approved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "rejected_by_user_id": { + "name": "rejected_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rejected_at": { + "name": "rejected_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": { + "join_requests_invite_unique_idx": { + "name": "join_requests_invite_unique_idx", + "columns": [ + { + "expression": "invite_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "join_requests_company_status_type_created_idx": { + "name": "join_requests_company_status_type_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "join_requests_pending_human_user_uq": { + "name": "join_requests_pending_human_user_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requesting_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"join_requests\".\"request_type\" = 'human' AND \"join_requests\".\"status\" = 'pending_approval' AND \"join_requests\".\"requesting_user_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "join_requests_pending_human_email_uq": { + "name": "join_requests_pending_human_email_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"request_email_snapshot\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"join_requests\".\"request_type\" = 'human' AND \"join_requests\".\"status\" = 'pending_approval' AND \"join_requests\".\"request_email_snapshot\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "join_requests_invite_id_invites_id_fk": { + "name": "join_requests_invite_id_invites_id_fk", + "tableFrom": "join_requests", + "tableTo": "invites", + "columnsFrom": [ + "invite_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "join_requests_company_id_companies_id_fk": { + "name": "join_requests_company_id_companies_id_fk", + "tableFrom": "join_requests", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "join_requests_created_agent_id_agents_id_fk": { + "name": "join_requests_created_agent_id_agents_id_fk", + "tableFrom": "join_requests", + "tableTo": "agents", + "columnsFrom": [ + "created_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.labels": { + "name": "labels", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "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": { + "labels_company_idx": { + "name": "labels_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "labels_company_name_idx": { + "name": "labels_company_name_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "labels_company_id_companies_id_fk": { + "name": "labels_company_id_companies_id_fk", + "tableFrom": "labels", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.managed_agent_profiles": { + "name": "managed_agent_profiles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "profile_key": { + "name": "profile_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "service": { + "name": "service", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'anthropic_managed_agents'" + }, + "anthropic_agent_id": { + "name": "anthropic_agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_version": { + "name": "agent_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "environment_id": { + "name": "environment_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "beta_version": { + "name": "beta_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'managed-agents-2026-04-01'" + }, + "default_model": { + "name": "default_model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'claude-sonnet-5'" + }, + "default_max_list_cost_cents": { + "name": "default_max_list_cost_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 100 + }, + "api_key_secret_id": { + "name": "api_key_secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "retention_acknowledged": { + "name": "retention_acknowledged", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "qualification": { + "name": "qualification", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "qualified_at": { + "name": "qualified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "qualified_revision": { + "name": "qualified_revision", + "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": { + "managed_agent_profiles_company_idx": { + "name": "managed_agent_profiles_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "managed_agent_profiles_company_key_uq": { + "name": "managed_agent_profiles_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "profile_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "managed_agent_profiles_company_resource_uq": { + "name": "managed_agent_profiles_company_resource_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "anthropic_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_version", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "managed_agent_profiles_company_id_companies_id_fk": { + "name": "managed_agent_profiles_company_id_companies_id_fk", + "tableFrom": "managed_agent_profiles", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "managed_agent_profiles_api_key_secret_id_company_secrets_id_fk": { + "name": "managed_agent_profiles_api_key_secret_id_company_secrets_id_fk", + "tableFrom": "managed_agent_profiles", + "tableTo": "company_secrets", + "columnsFrom": [ + "api_key_secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "managed_agent_profiles_service_check": { + "name": "managed_agent_profiles_service_check", + "value": "\"managed_agent_profiles\".\"service\" = 'anthropic_managed_agents'" + }, + "managed_agent_profiles_beta_check": { + "name": "managed_agent_profiles_beta_check", + "value": "\"managed_agent_profiles\".\"beta_version\" = 'managed-agents-2026-04-01'" + }, + "managed_agent_profiles_positive_budget_check": { + "name": "managed_agent_profiles_positive_budget_check", + "value": "\"managed_agent_profiles\".\"default_max_list_cost_cents\" > 0" + }, + "managed_agent_profiles_qualified_revision_check": { + "name": "managed_agent_profiles_qualified_revision_check", + "value": "(\"managed_agent_profiles\".\"qualified_at\" IS NULL AND \"managed_agent_profiles\".\"qualified_revision\" IS NULL) OR (\"managed_agent_profiles\".\"qualified_at\" IS NOT NULL AND \"managed_agent_profiles\".\"qualification\" <> '{}'::jsonb AND \"managed_agent_profiles\".\"qualified_revision\" ~ '^sha256:[0-9a-f]{64}$')" + } + }, + "isRLSEnabled": false + }, + "public.native_run_finalizations": { + "name": "native_run_finalizations", + "schema": "", + "columns": { + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "attempt": { + "name": "attempt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "lease_owner": { + "name": "lease_owner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lease_expires_at": { + "name": "lease_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "controller_boot_id": { + "name": "controller_boot_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "controller_pid": { + "name": "controller_pid", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "controller_process_started_at": { + "name": "controller_process_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "controller_generation": { + "name": "controller_generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "recovery_state": { + "name": "recovery_state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "recovery_request_id": { + "name": "recovery_request_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "recovery_history": { + "name": "recovery_history", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "result_id": { + "name": "result_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "assessment_id": { + "name": "assessment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "decision_id": { + "name": "decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "failure_code": { + "name": "failure_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "failure_detail": { + "name": "failure_detail", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "control_deadline_at": { + "name": "control_deadline_at", + "type": "timestamp with time zone", + "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": { + "native_run_finalizations_control_deadline_idx": { + "name": "native_run_finalizations_control_deadline_idx", + "columns": [ + { + "expression": "control_deadline_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"native_run_finalizations\".\"control_deadline_at\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "native_run_finalizations_company_id_companies_id_fk": { + "name": "native_run_finalizations_company_id_companies_id_fk", + "tableFrom": "native_run_finalizations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_finalizations_issue_company_fk": { + "name": "native_run_finalizations_issue_company_fk", + "tableFrom": "native_run_finalizations", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_finalizations_run_owner_fk": { + "name": "native_run_finalizations_run_owner_fk", + "tableFrom": "native_run_finalizations", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id" + ], + "columnsTo": [ + "company_id", + "native_issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_finalizations_result_owner_fk": { + "name": "native_run_finalizations_result_owner_fk", + "tableFrom": "native_run_finalizations", + "tableTo": "native_run_results", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "result_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "run_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_finalizations_assessment_owner_fk": { + "name": "native_run_finalizations_assessment_owner_fk", + "tableFrom": "native_run_finalizations", + "tableTo": "work_assessments", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "assessment_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "run_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_finalizations_decision_owner_fk": { + "name": "native_run_finalizations_decision_owner_fk", + "tableFrom": "native_run_finalizations", + "tableTo": "status_decisions", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "assessment_id", + "decision_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "run_id", + "assessment_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "native_run_finalizations_assessment_requires_result_check": { + "name": "native_run_finalizations_assessment_requires_result_check", + "value": "\"native_run_finalizations\".\"assessment_id\" is null or \"native_run_finalizations\".\"result_id\" is not null" + }, + "native_run_finalizations_decision_requires_assessment_check": { + "name": "native_run_finalizations_decision_requires_assessment_check", + "value": "\"native_run_finalizations\".\"decision_id\" is null or \"native_run_finalizations\".\"assessment_id\" is not null" + } + }, + "isRLSEnabled": false + }, + "public.native_run_results": { + "name": "native_run_results", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "turn_id": { + "name": "turn_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completion_contract_id": { + "name": "completion_contract_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "caller_result_id": { + "name": "caller_result_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "caller_dedupe_key": { + "name": "caller_dedupe_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "server_fingerprint": { + "name": "server_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schema_status": { + "name": "schema_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rejection_code": { + "name": "rejection_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_json": { + "name": "result_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "canonical_sha256": { + "name": "canonical_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "native_run_results_run_fingerprint_uq": { + "name": "native_run_results_run_fingerprint_uq", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "server_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "native_run_results_run_caller_result_uq": { + "name": "native_run_results_run_caller_result_uq", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "caller_result_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "native_run_results_run_caller_dedupe_uq": { + "name": "native_run_results_run_caller_dedupe_uq", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "caller_dedupe_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "native_run_results_company_id_companies_id_fk": { + "name": "native_run_results_company_id_companies_id_fk", + "tableFrom": "native_run_results", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_results_issue_company_fk": { + "name": "native_run_results_issue_company_fk", + "tableFrom": "native_run_results", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_results_run_contract_owner_fk": { + "name": "native_run_results_run_contract_owner_fk", + "tableFrom": "native_run_results", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "completion_contract_id" + ], + "columnsTo": [ + "company_id", + "native_issue_id", + "id", + "completion_contract_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_results_completion_contract_owner_fk": { + "name": "native_run_results_completion_contract_owner_fk", + "tableFrom": "native_run_results", + "tableTo": "completion_contracts", + "columnsFrom": [ + "company_id", + "issue_id", + "completion_contract_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "native_run_results_company_issue_run_id_uq": { + "name": "native_run_results_company_issue_run_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "issue_id", + "run_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pipeline_automation_executions": { + "name": "pipeline_automation_executions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "automation_id": { + "name": "automation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "triggering_event_id": { + "name": "triggering_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_issue_id": { + "name": "execution_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "retry_of_execution_id": { + "name": "retry_of_execution_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "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": { + "pipeline_automation_executions_idempotency_uq": { + "name": "pipeline_automation_executions_idempotency_uq", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "automation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "triggering_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_automation_executions_company_case_idx": { + "name": "pipeline_automation_executions_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_automation_executions_routine_idx": { + "name": "pipeline_automation_executions_routine_idx", + "columns": [ + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_automation_executions_execution_issue_idx": { + "name": "pipeline_automation_executions_execution_issue_idx", + "columns": [ + { + "expression": "execution_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_automation_executions_retry_of_execution_idx": { + "name": "pipeline_automation_executions_retry_of_execution_idx", + "columns": [ + { + "expression": "retry_of_execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_automation_executions_company_id_companies_id_fk": { + "name": "pipeline_automation_executions_company_id_companies_id_fk", + "tableFrom": "pipeline_automation_executions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_automation_executions_case_id_pipeline_cases_id_fk": { + "name": "pipeline_automation_executions_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_automation_executions", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_automation_executions_routine_id_routines_id_fk": { + "name": "pipeline_automation_executions_routine_id_routines_id_fk", + "tableFrom": "pipeline_automation_executions", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_automation_executions_execution_issue_id_issues_id_fk": { + "name": "pipeline_automation_executions_execution_issue_id_issues_id_fk", + "tableFrom": "pipeline_automation_executions", + "tableTo": "issues", + "columnsFrom": [ + "execution_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pipeline_automation_executions_status_check": { + "name": "pipeline_automation_executions_status_check", + "value": "\"pipeline_automation_executions\".\"status\" in ('succeeded', 'failed')" + } + }, + "isRLSEnabled": false + }, + "public.pipeline_case_blockers": { + "name": "pipeline_case_blockers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "blocked_by_case_id": { + "name": "blocked_by_case_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": { + "pipeline_case_blockers_case_blocked_by_uq": { + "name": "pipeline_case_blockers_case_blocked_by_uq", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "blocked_by_case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_blockers_blocked_by_idx": { + "name": "pipeline_case_blockers_blocked_by_idx", + "columns": [ + { + "expression": "blocked_by_case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_blockers_company_case_idx": { + "name": "pipeline_case_blockers_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_case_blockers_company_id_companies_id_fk": { + "name": "pipeline_case_blockers_company_id_companies_id_fk", + "tableFrom": "pipeline_case_blockers", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_blockers_case_id_pipeline_cases_id_fk": { + "name": "pipeline_case_blockers_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_case_blockers", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_blockers_blocked_by_case_id_pipeline_cases_id_fk": { + "name": "pipeline_case_blockers_blocked_by_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_case_blockers", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "blocked_by_case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pipeline_case_blockers_no_self_block_check": { + "name": "pipeline_case_blockers_no_self_block_check", + "value": "\"pipeline_case_blockers\".\"case_id\" <> \"pipeline_case_blockers\".\"blocked_by_case_id\"" + } + }, + "isRLSEnabled": false + }, + "public.pipeline_case_documents": { + "name": "pipeline_case_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "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": { + "pipeline_case_documents_company_case_key_uq": { + "name": "pipeline_case_documents_company_case_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_documents_document_uq": { + "name": "pipeline_case_documents_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_documents_company_case_updated_idx": { + "name": "pipeline_case_documents_company_case_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_case_documents_company_id_companies_id_fk": { + "name": "pipeline_case_documents_company_id_companies_id_fk", + "tableFrom": "pipeline_case_documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_documents_case_id_pipeline_cases_id_fk": { + "name": "pipeline_case_documents_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_case_documents", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_documents_document_id_documents_id_fk": { + "name": "pipeline_case_documents_document_id_documents_id_fk", + "tableFrom": "pipeline_case_documents", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pipeline_case_events": { + "name": "pipeline_case_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_agent_id": { + "name": "actor_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "from_stage_id": { + "name": "from_stage_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "to_stage_id": { + "name": "to_stage_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "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()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_case_events_case_created_idx": { + "name": "pipeline_case_events_case_created_idx", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_events_company_case_idx": { + "name": "pipeline_case_events_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_case_events_company_id_companies_id_fk": { + "name": "pipeline_case_events_company_id_companies_id_fk", + "tableFrom": "pipeline_case_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_events_case_id_pipeline_cases_id_fk": { + "name": "pipeline_case_events_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_case_events", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_events_actor_agent_id_agents_id_fk": { + "name": "pipeline_case_events_actor_agent_id_agents_id_fk", + "tableFrom": "pipeline_case_events", + "tableTo": "agents", + "columnsFrom": [ + "actor_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "pipeline_case_events_from_stage_id_pipeline_stages_id_fk": { + "name": "pipeline_case_events_from_stage_id_pipeline_stages_id_fk", + "tableFrom": "pipeline_case_events", + "tableTo": "pipeline_stages", + "columnsFrom": [ + "from_stage_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "pipeline_case_events_to_stage_id_pipeline_stages_id_fk": { + "name": "pipeline_case_events_to_stage_id_pipeline_stages_id_fk", + "tableFrom": "pipeline_case_events", + "tableTo": "pipeline_stages", + "columnsFrom": [ + "to_stage_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pipeline_case_events_type_check": { + "name": "pipeline_case_events_type_check", + "value": "\"pipeline_case_events\".\"type\" in (\n 'ingested',\n 'updated',\n 'claimed',\n 'lease_released',\n 'lease_expired',\n 'transitioned',\n 'transition_forced',\n 'transition_suggested',\n 'suggestion_resolved',\n 'review_decided',\n 'conversation_opened',\n 'issue_linked',\n 'issue_unlinked',\n 'automation_executed',\n 'automation_failed',\n 'automation_retry_requested',\n 'automation_effects_retired',\n 'automation_retry_dispatched',\n 'blockers_set',\n 'blockers_resolved',\n 'children_terminal',\n 'upstream_drift',\n 'drift_acknowledged'\n )" + }, + "pipeline_case_events_actor_type_check": { + "name": "pipeline_case_events_actor_type_check", + "value": "\"pipeline_case_events\".\"actor_type\" in ('user', 'agent', 'system')" + }, + "pipeline_case_events_agent_run_check": { + "name": "pipeline_case_events_agent_run_check", + "value": "\"pipeline_case_events\".\"actor_type\" <> 'agent' or \"pipeline_case_events\".\"run_id\" is not null" + } + }, + "isRLSEnabled": false + }, + "public.pipeline_case_issue_links": { + "name": "pipeline_case_issue_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "automation_attempt_id": { + "name": "automation_attempt_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "retired_at": { + "name": "retired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "retired_by_attempt_id": { + "name": "retired_by_attempt_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "retired_reason": { + "name": "retired_reason", + "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": { + "pipeline_case_issue_links_case_issue_uq": { + "name": "pipeline_case_issue_links_case_issue_uq", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_issue_links_issue_idx": { + "name": "pipeline_case_issue_links_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_issue_links_company_case_idx": { + "name": "pipeline_case_issue_links_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_issue_links_automation_attempt_idx": { + "name": "pipeline_case_issue_links_automation_attempt_idx", + "columns": [ + { + "expression": "automation_attempt_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_case_issue_links_company_id_companies_id_fk": { + "name": "pipeline_case_issue_links_company_id_companies_id_fk", + "tableFrom": "pipeline_case_issue_links", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_issue_links_case_id_pipeline_cases_id_fk": { + "name": "pipeline_case_issue_links_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_case_issue_links", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_issue_links_issue_id_issues_id_fk": { + "name": "pipeline_case_issue_links_issue_id_issues_id_fk", + "tableFrom": "pipeline_case_issue_links", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pipeline_case_issue_links_role_check": { + "name": "pipeline_case_issue_links_role_check", + "value": "\"pipeline_case_issue_links\".\"role\" in ('origin', 'conversation', 'work', 'automation')" + } + }, + "isRLSEnabled": false + }, + "public.pipeline_cases": { + "name": "pipeline_cases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pipeline_id": { + "name": "pipeline_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "stage_id": { + "name": "stage_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_key": { + "name": "case_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fields": { + "name": "fields", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "workspace_ref": { + "name": "workspace_ref", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "parent_case_id": { + "name": "parent_case_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_case_version": { + "name": "parent_case_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "request_key": { + "name": "request_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "automation_attempt_id": { + "name": "automation_attempt_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "pending_suggestion": { + "name": "pending_suggestion", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "lease_owner_type": { + "name": "lease_owner_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lease_agent_id": { + "name": "lease_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "lease_user_id": { + "name": "lease_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lease_token": { + "name": "lease_token", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "lease_expires_at": { + "name": "lease_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "terminal_kind": { + "name": "terminal_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "terminal_at": { + "name": "terminal_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "retired_at": { + "name": "retired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "retired_by_attempt_id": { + "name": "retired_by_attempt_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "retired_reason": { + "name": "retired_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "hidden_from_board_at": { + "name": "hidden_from_board_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "child_count": { + "name": "child_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "terminal_child_count": { + "name": "terminal_child_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "origin_run_id": { + "name": "origin_run_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": { + "pipeline_cases_pipeline_case_key_uq": { + "name": "pipeline_cases_pipeline_case_key_uq", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_parent_request_key_uq": { + "name": "pipeline_cases_parent_request_key_uq", + "columns": [ + { + "expression": "parent_case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"pipeline_cases\".\"request_key\" is not null and \"pipeline_cases\".\"retired_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_company_idx": { + "name": "pipeline_cases_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_pipeline_stage_idx": { + "name": "pipeline_cases_pipeline_stage_idx", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_parent_idx": { + "name": "pipeline_cases_parent_idx", + "columns": [ + { + "expression": "parent_case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_automation_attempt_idx": { + "name": "pipeline_cases_automation_attempt_idx", + "columns": [ + { + "expression": "automation_attempt_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_retired_idx": { + "name": "pipeline_cases_retired_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "retired_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_lease_expires_idx": { + "name": "pipeline_cases_lease_expires_idx", + "columns": [ + { + "expression": "lease_expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"pipeline_cases\".\"lease_expires_at\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_cases_company_id_companies_id_fk": { + "name": "pipeline_cases_company_id_companies_id_fk", + "tableFrom": "pipeline_cases", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_cases_pipeline_id_pipelines_id_fk": { + "name": "pipeline_cases_pipeline_id_pipelines_id_fk", + "tableFrom": "pipeline_cases", + "tableTo": "pipelines", + "columnsFrom": [ + "pipeline_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_cases_stage_id_pipeline_stages_id_fk": { + "name": "pipeline_cases_stage_id_pipeline_stages_id_fk", + "tableFrom": "pipeline_cases", + "tableTo": "pipeline_stages", + "columnsFrom": [ + "stage_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "pipeline_cases_parent_case_id_pipeline_cases_id_fk": { + "name": "pipeline_cases_parent_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_cases", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "parent_case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "pipeline_cases_lease_agent_id_agents_id_fk": { + "name": "pipeline_cases_lease_agent_id_agents_id_fk", + "tableFrom": "pipeline_cases", + "tableTo": "agents", + "columnsFrom": [ + "lease_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "pipeline_cases_created_by_agent_id_agents_id_fk": { + "name": "pipeline_cases_created_by_agent_id_agents_id_fk", + "tableFrom": "pipeline_cases", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pipeline_cases_terminal_kind_check": { + "name": "pipeline_cases_terminal_kind_check", + "value": "\"pipeline_cases\".\"terminal_kind\" is null or \"pipeline_cases\".\"terminal_kind\" in ('done', 'cancelled')" + }, + "pipeline_cases_lease_owner_type_check": { + "name": "pipeline_cases_lease_owner_type_check", + "value": "\"pipeline_cases\".\"lease_owner_type\" is null or \"pipeline_cases\".\"lease_owner_type\" in ('user', 'agent')" + } + }, + "isRLSEnabled": false + }, + "public.pipeline_documents": { + "name": "pipeline_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pipeline_id": { + "name": "pipeline_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "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": { + "pipeline_documents_company_pipeline_key_uq": { + "name": "pipeline_documents_company_pipeline_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_documents_document_uq": { + "name": "pipeline_documents_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_documents_company_pipeline_updated_idx": { + "name": "pipeline_documents_company_pipeline_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_documents_company_id_companies_id_fk": { + "name": "pipeline_documents_company_id_companies_id_fk", + "tableFrom": "pipeline_documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_documents_pipeline_id_pipelines_id_fk": { + "name": "pipeline_documents_pipeline_id_pipelines_id_fk", + "tableFrom": "pipeline_documents", + "tableTo": "pipelines", + "columnsFrom": [ + "pipeline_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_documents_document_id_documents_id_fk": { + "name": "pipeline_documents_document_id_documents_id_fk", + "tableFrom": "pipeline_documents", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pipeline_stages": { + "name": "pipeline_stages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "pipeline_id": { + "name": "pipeline_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_stages_pipeline_key_uq": { + "name": "pipeline_stages_pipeline_key_uq", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_stages_pipeline_position_idx": { + "name": "pipeline_stages_pipeline_position_idx", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_stages_pipeline_id_pipelines_id_fk": { + "name": "pipeline_stages_pipeline_id_pipelines_id_fk", + "tableFrom": "pipeline_stages", + "tableTo": "pipelines", + "columnsFrom": [ + "pipeline_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pipeline_stages_kind_check": { + "name": "pipeline_stages_kind_check", + "value": "\"pipeline_stages\".\"kind\" in ('working', 'review', 'done', 'cancelled')" + } + }, + "isRLSEnabled": false + }, + "public.pipeline_transitions": { + "name": "pipeline_transitions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "pipeline_id": { + "name": "pipeline_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "from_stage_id": { + "name": "from_stage_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "to_stage_id": { + "name": "to_stage_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "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": { + "pipeline_transitions_pipeline_edge_uq": { + "name": "pipeline_transitions_pipeline_edge_uq", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "from_stage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "to_stage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_transitions_pipeline_from_idx": { + "name": "pipeline_transitions_pipeline_from_idx", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "from_stage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_transitions_pipeline_to_idx": { + "name": "pipeline_transitions_pipeline_to_idx", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "to_stage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_transitions_pipeline_id_pipelines_id_fk": { + "name": "pipeline_transitions_pipeline_id_pipelines_id_fk", + "tableFrom": "pipeline_transitions", + "tableTo": "pipelines", + "columnsFrom": [ + "pipeline_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_transitions_from_stage_id_pipeline_stages_id_fk": { + "name": "pipeline_transitions_from_stage_id_pipeline_stages_id_fk", + "tableFrom": "pipeline_transitions", + "tableTo": "pipeline_stages", + "columnsFrom": [ + "from_stage_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_transitions_to_stage_id_pipeline_stages_id_fk": { + "name": "pipeline_transitions_to_stage_id_pipeline_stages_id_fk", + "tableFrom": "pipeline_transitions", + "tableTo": "pipeline_stages", + "columnsFrom": [ + "to_stage_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pipelines": { + "name": "pipelines", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enforce_transitions": { + "name": "enforce_transitions", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "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": { + "pipelines_company_key_uq": { + "name": "pipelines_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipelines_company_idx": { + "name": "pipelines_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipelines_company_project_idx": { + "name": "pipelines_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipelines_company_id_companies_id_fk": { + "name": "pipelines_company_id_companies_id_fk", + "tableFrom": "pipelines", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipelines_project_id_projects_id_fk": { + "name": "pipelines_project_id_projects_id_fk", + "tableFrom": "pipelines", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "pipelines_created_by_agent_id_agents_id_fk": { + "name": "pipelines_created_by_agent_id_agents_id_fk", + "tableFrom": "pipelines", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_company_settings": { + "name": "plugin_company_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "settings_json": { + "name": "settings_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "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": { + "plugin_company_settings_company_idx": { + "name": "plugin_company_settings_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_company_settings_plugin_idx": { + "name": "plugin_company_settings_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_company_settings_company_plugin_uq": { + "name": "plugin_company_settings_company_plugin_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_company_settings_company_id_companies_id_fk": { + "name": "plugin_company_settings_company_id_companies_id_fk", + "tableFrom": "plugin_company_settings", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_company_settings_plugin_id_plugins_id_fk": { + "name": "plugin_company_settings_plugin_id_plugins_id_fk", + "tableFrom": "plugin_company_settings", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_config": { + "name": "plugin_config", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "config_json": { + "name": "config_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "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": { + "plugin_config_plugin_company_idx": { + "name": "plugin_config_plugin_company_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_config_plugin_id_plugins_id_fk": { + "name": "plugin_config_plugin_id_plugins_id_fk", + "tableFrom": "plugin_config", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_config_company_id_companies_id_fk": { + "name": "plugin_config_company_id_companies_id_fk", + "tableFrom": "plugin_config", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_database_namespaces": { + "name": "plugin_database_namespaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_key": { + "name": "plugin_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "namespace_name": { + "name": "namespace_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "namespace_mode": { + "name": "namespace_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'schema'" + }, + "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()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_database_namespaces_plugin_idx": { + "name": "plugin_database_namespaces_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_database_namespaces_namespace_idx": { + "name": "plugin_database_namespaces_namespace_idx", + "columns": [ + { + "expression": "namespace_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_database_namespaces_status_idx": { + "name": "plugin_database_namespaces_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_database_namespaces_plugin_id_plugins_id_fk": { + "name": "plugin_database_namespaces_plugin_id_plugins_id_fk", + "tableFrom": "plugin_database_namespaces", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_entities": { + "name": "plugin_entities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_kind": { + "name": "scope_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_entities_plugin_idx": { + "name": "plugin_entities_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_company_idx": { + "name": "plugin_entities_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_type_idx": { + "name": "plugin_entities_type_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_scope_idx": { + "name": "plugin_entities_scope_idx", + "columns": [ + { + "expression": "scope_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_entities_plugin_id_plugins_id_fk": { + "name": "plugin_entities_plugin_id_plugins_id_fk", + "tableFrom": "plugin_entities", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_entities_company_id_companies_id_fk": { + "name": "plugin_entities_company_id_companies_id_fk", + "tableFrom": "plugin_entities", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "plugin_entities_external_idx": { + "name": "plugin_entities_external_idx", + "nullsNotDistinct": true, + "columns": [ + "company_id", + "plugin_id", + "entity_type", + "external_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_job_runs": { + "name": "plugin_job_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "job_id": { + "name": "job_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "logs": { + "name": "logs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_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": { + "plugin_job_runs_job_idx": { + "name": "plugin_job_runs_job_idx", + "columns": [ + { + "expression": "job_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_job_runs_plugin_idx": { + "name": "plugin_job_runs_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_job_runs_company_idx": { + "name": "plugin_job_runs_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_job_runs_status_idx": { + "name": "plugin_job_runs_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_job_runs_job_id_plugin_jobs_id_fk": { + "name": "plugin_job_runs_job_id_plugin_jobs_id_fk", + "tableFrom": "plugin_job_runs", + "tableTo": "plugin_jobs", + "columnsFrom": [ + "job_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_job_runs_plugin_id_plugins_id_fk": { + "name": "plugin_job_runs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_job_runs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_job_runs_company_id_companies_id_fk": { + "name": "plugin_job_runs_company_id_companies_id_fk", + "tableFrom": "plugin_job_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_jobs": { + "name": "plugin_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "job_key": { + "name": "job_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schedule": { + "name": "schedule", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "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": { + "plugin_jobs_plugin_idx": { + "name": "plugin_jobs_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_jobs_next_run_idx": { + "name": "plugin_jobs_next_run_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_jobs_unique_idx": { + "name": "plugin_jobs_unique_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "job_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_jobs_plugin_id_plugins_id_fk": { + "name": "plugin_jobs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_jobs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_logs": { + "name": "plugin_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'info'" + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "meta": { + "name": "meta", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_logs_plugin_time_idx": { + "name": "plugin_logs_plugin_time_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_logs_company_idx": { + "name": "plugin_logs_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_logs_level_idx": { + "name": "plugin_logs_level_idx", + "columns": [ + { + "expression": "level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_logs_plugin_id_plugins_id_fk": { + "name": "plugin_logs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_logs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_logs_company_id_companies_id_fk": { + "name": "plugin_logs_company_id_companies_id_fk", + "tableFrom": "plugin_logs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_managed_resources": { + "name": "plugin_managed_resources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_key": { + "name": "plugin_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_kind": { + "name": "resource_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_key": { + "name": "resource_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "defaults_json": { + "name": "defaults_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_managed_resources_company_idx": { + "name": "plugin_managed_resources_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_managed_resources_plugin_idx": { + "name": "plugin_managed_resources_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_managed_resources_resource_idx": { + "name": "plugin_managed_resources_resource_idx", + "columns": [ + { + "expression": "resource_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_managed_resources_company_plugin_resource_uq": { + "name": "plugin_managed_resources_company_plugin_resource_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_managed_resources_company_id_companies_id_fk": { + "name": "plugin_managed_resources_company_id_companies_id_fk", + "tableFrom": "plugin_managed_resources", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_managed_resources_plugin_id_plugins_id_fk": { + "name": "plugin_managed_resources_plugin_id_plugins_id_fk", + "tableFrom": "plugin_managed_resources", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_migrations": { + "name": "plugin_migrations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_key": { + "name": "plugin_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "namespace_name": { + "name": "namespace_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "migration_key": { + "name": "migration_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "checksum": { + "name": "checksum", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "plugin_version": { + "name": "plugin_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "applied_at": { + "name": "applied_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "plugin_migrations_plugin_key_idx": { + "name": "plugin_migrations_plugin_key_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "migration_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_migrations_plugin_idx": { + "name": "plugin_migrations_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_migrations_status_idx": { + "name": "plugin_migrations_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_migrations_plugin_id_plugins_id_fk": { + "name": "plugin_migrations_plugin_id_plugins_id_fk", + "tableFrom": "plugin_migrations", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_state": { + "name": "plugin_state", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_kind": { + "name": "scope_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "namespace": { + "name": "namespace", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "state_key": { + "name": "state_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value_json": { + "name": "value_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_state_plugin_scope_idx": { + "name": "plugin_state_plugin_scope_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_state_plugin_id_plugins_id_fk": { + "name": "plugin_state_plugin_id_plugins_id_fk", + "tableFrom": "plugin_state", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "plugin_state_unique_entry_idx": { + "name": "plugin_state_unique_entry_idx", + "nullsNotDistinct": true, + "columns": [ + "plugin_id", + "scope_kind", + "scope_id", + "namespace", + "state_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_webhook_deliveries": { + "name": "plugin_webhook_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "webhook_key": { + "name": "webhook_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "headers": { + "name": "headers", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_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": { + "plugin_webhook_deliveries_plugin_idx": { + "name": "plugin_webhook_deliveries_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_webhook_deliveries_company_idx": { + "name": "plugin_webhook_deliveries_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_webhook_deliveries_status_idx": { + "name": "plugin_webhook_deliveries_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_webhook_deliveries_key_idx": { + "name": "plugin_webhook_deliveries_key_idx", + "columns": [ + { + "expression": "webhook_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_webhook_deliveries_plugin_id_plugins_id_fk": { + "name": "plugin_webhook_deliveries_plugin_id_plugins_id_fk", + "tableFrom": "plugin_webhook_deliveries", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_webhook_deliveries_company_id_companies_id_fk": { + "name": "plugin_webhook_deliveries_company_id_companies_id_fk", + "tableFrom": "plugin_webhook_deliveries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugins": { + "name": "plugins", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_key": { + "name": "plugin_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "package_name": { + "name": "package_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "api_version": { + "name": "api_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "categories": { + "name": "categories", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "manifest_json": { + "name": "manifest_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'installed'" + }, + "install_order": { + "name": "install_order", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "package_path": { + "name": "package_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "installed_at": { + "name": "installed_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": { + "plugins_plugin_key_idx": { + "name": "plugins_plugin_key_idx", + "columns": [ + { + "expression": "plugin_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugins_status_idx": { + "name": "plugins_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.principal_permission_grants": { + "name": "principal_permission_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "principal_type": { + "name": "principal_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission_key": { + "name": "permission_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "granted_by_user_id": { + "name": "granted_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "principal_permission_grants_unique_idx": { + "name": "principal_permission_grants_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "principal_permission_grants_company_permission_idx": { + "name": "principal_permission_grants_company_permission_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "principal_permission_grants_company_id_companies_id_fk": { + "name": "principal_permission_grants_company_id_companies_id_fk", + "tableFrom": "principal_permission_grants", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_goals": { + "name": "project_goals", + "schema": "", + "columns": { + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_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": { + "project_goals_project_idx": { + "name": "project_goals_project_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_goals_goal_idx": { + "name": "project_goals_goal_idx", + "columns": [ + { + "expression": "goal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_goals_company_idx": { + "name": "project_goals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_goals_project_id_projects_id_fk": { + "name": "project_goals_project_id_projects_id_fk", + "tableFrom": "project_goals", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_goals_goal_id_goals_id_fk": { + "name": "project_goals_goal_id_goals_id_fk", + "tableFrom": "project_goals", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_goals_company_id_companies_id_fk": { + "name": "project_goals_company_id_companies_id_fk", + "tableFrom": "project_goals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "project_goals_project_id_goal_id_pk": { + "name": "project_goals_project_id_goal_id_pk", + "columns": [ + "project_id", + "goal_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_memberships": { + "name": "project_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'joined'" + }, + "starred_at": { + "name": "starred_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": { + "project_memberships_company_user_idx": { + "name": "project_memberships_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_memberships_company_user_starred_idx": { + "name": "project_memberships_company_user_starred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "starred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_memberships_project_idx": { + "name": "project_memberships_project_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_memberships_company_user_project_uq": { + "name": "project_memberships_company_user_project_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_memberships_company_id_companies_id_fk": { + "name": "project_memberships_company_id_companies_id_fk", + "tableFrom": "project_memberships", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_memberships_project_id_projects_id_fk": { + "name": "project_memberships_project_id_projects_id_fk", + "tableFrom": "project_memberships", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_workspaces": { + "name": "project_workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_path'" + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_url": { + "name": "repo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_ref": { + "name": "repo_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "default_ref": { + "name": "default_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "visibility": { + "name": "visibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "setup_command": { + "name": "setup_command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cleanup_command": { + "name": "cleanup_command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "remote_provider": { + "name": "remote_provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "remote_workspace_ref": { + "name": "remote_workspace_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shared_workspace_key": { + "name": "shared_workspace_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_primary": { + "name": "is_primary", + "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": { + "project_workspaces_company_project_idx": { + "name": "project_workspaces_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_primary_idx": { + "name": "project_workspaces_project_primary_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_primary", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_source_type_idx": { + "name": "project_workspaces_project_source_type_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_company_shared_key_idx": { + "name": "project_workspaces_company_shared_key_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "shared_workspace_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_remote_ref_idx": { + "name": "project_workspaces_project_remote_ref_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "remote_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "remote_workspace_ref", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_workspaces_company_id_companies_id_fk": { + "name": "project_workspaces_company_id_companies_id_fk", + "tableFrom": "project_workspaces", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "project_workspaces_project_id_projects_id_fk": { + "name": "project_workspaces_project_id_projects_id_fk", + "tableFrom": "project_workspaces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.projects": { + "name": "projects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'backlog'" + }, + "lead_agent_id": { + "name": "lead_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_date": { + "name": "target_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_policy": { + "name": "execution_workspace_policy", + "type": "jsonb", + "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": { + "projects_company_idx": { + "name": "projects_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "projects_company_id_companies_id_fk": { + "name": "projects_company_id_companies_id_fk", + "tableFrom": "projects", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "projects_goal_id_goals_id_fk": { + "name": "projects_goal_id_goals_id_fk", + "tableFrom": "projects", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "projects_lead_agent_id_agents_id_fk": { + "name": "projects_lead_agent_id_agents_id_fk", + "tableFrom": "projects", + "tableTo": "agents", + "columnsFrom": [ + "lead_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_trace_records": { + "name": "provider_trace_records", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'capturing'" + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trace_ref": { + "name": "trace_ref", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "frame_count": { + "name": "frame_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "byte_count": { + "name": "byte_count", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "digest": { + "name": "digest", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_by": { + "name": "requested_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_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": { + "provider_trace_records_run_unique": { + "name": "provider_trace_records_run_unique", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "provider_trace_records_expiry_idx": { + "name": "provider_trace_records_expiry_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "provider_trace_records_company_created_idx": { + "name": "provider_trace_records_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "provider_trace_records_company_id_companies_id_fk": { + "name": "provider_trace_records_company_id_companies_id_fk", + "tableFrom": "provider_trace_records", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "provider_trace_records_run_id_heartbeat_runs_id_fk": { + "name": "provider_trace_records_run_id_heartbeat_runs_id_fk", + "tableFrom": "provider_trace_records", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.remote_agent_profiles": { + "name": "remote_agent_profiles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "profile_key": { + "name": "profile_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "service": { + "name": "service", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "configuration": { + "name": "configuration", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "retention_acknowledged": { + "name": "retention_acknowledged", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "qualification": { + "name": "qualification", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "qualified_at": { + "name": "qualified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "qualified_revision": { + "name": "qualified_revision", + "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": { + "remote_agent_profiles_company_idx": { + "name": "remote_agent_profiles_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "remote_agent_profiles_company_key_uq": { + "name": "remote_agent_profiles_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "profile_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "remote_agent_profiles_company_id_companies_id_fk": { + "name": "remote_agent_profiles_company_id_companies_id_fk", + "tableFrom": "remote_agent_profiles", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "remote_agent_profiles_service_check": { + "name": "remote_agent_profiles_service_check", + "value": "\"remote_agent_profiles\".\"service\" = 'aws_bedrock_agentcore_harness'" + }, + "remote_agent_profiles_qualified_revision_check": { + "name": "remote_agent_profiles_qualified_revision_check", + "value": "(\"remote_agent_profiles\".\"qualified_at\" IS NULL AND \"remote_agent_profiles\".\"qualified_revision\" IS NULL) OR (\"remote_agent_profiles\".\"qualified_at\" IS NOT NULL AND \"remote_agent_profiles\".\"qualification\" <> '{}'::jsonb AND \"remote_agent_profiles\".\"qualified_revision\" ~ '^sha256:[0-9a-f]{64}$')" + } + }, + "isRLSEnabled": false + }, + "public.routine_documents": { + "name": "routine_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "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": { + "routine_documents_company_routine_key_uq": { + "name": "routine_documents_company_routine_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_documents_document_uq": { + "name": "routine_documents_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_documents_company_routine_updated_idx": { + "name": "routine_documents_company_routine_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_documents_company_id_companies_id_fk": { + "name": "routine_documents_company_id_companies_id_fk", + "tableFrom": "routine_documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "routine_documents_routine_id_routines_id_fk": { + "name": "routine_documents_routine_id_routines_id_fk", + "tableFrom": "routine_documents", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_documents_document_id_documents_id_fk": { + "name": "routine_documents_document_id_documents_id_fk", + "tableFrom": "routine_documents", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_revisions": { + "name": "routine_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision_number": { + "name": "revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "snapshot": { + "name": "snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "change_summary": { + "name": "change_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "restored_from_revision_id": { + "name": "restored_from_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routine_revisions_routine_revision_uq": { + "name": "routine_revisions_routine_revision_uq", + "columns": [ + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_revisions_company_routine_created_idx": { + "name": "routine_revisions_company_routine_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_revisions_company_responsible_user_idx": { + "name": "routine_revisions_company_responsible_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "responsible_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_revisions_company_id_companies_id_fk": { + "name": "routine_revisions_company_id_companies_id_fk", + "tableFrom": "routine_revisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_revisions_routine_id_routines_id_fk": { + "name": "routine_revisions_routine_id_routines_id_fk", + "tableFrom": "routine_revisions", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_revisions_restored_from_revision_id_routine_revisions_id_fk": { + "name": "routine_revisions_restored_from_revision_id_routine_revisions_id_fk", + "tableFrom": "routine_revisions", + "tableTo": "routine_revisions", + "columnsFrom": [ + "restored_from_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_revisions_created_by_agent_id_agents_id_fk": { + "name": "routine_revisions_created_by_agent_id_agents_id_fk", + "tableFrom": "routine_revisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_revisions_created_by_run_id_heartbeat_runs_id_fk": { + "name": "routine_revisions_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "routine_revisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_runs": { + "name": "routine_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trigger_id": { + "name": "trigger_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "triggered_at": { + "name": "triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "routine_revision_id": { + "name": "routine_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trigger_payload": { + "name": "trigger_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "dispatch_fingerprint": { + "name": "dispatch_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "linked_issue_id": { + "name": "linked_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "coalesced_into_run_id": { + "name": "coalesced_into_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "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": { + "routine_runs_company_routine_idx": { + "name": "routine_runs_company_routine_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_revision_idx": { + "name": "routine_runs_revision_idx", + "columns": [ + { + "expression": "routine_revision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_company_responsible_user_idx": { + "name": "routine_runs_company_responsible_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "responsible_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_trigger_idx": { + "name": "routine_runs_trigger_idx", + "columns": [ + { + "expression": "trigger_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_dispatch_fingerprint_idx": { + "name": "routine_runs_dispatch_fingerprint_idx", + "columns": [ + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "dispatch_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_linked_issue_idx": { + "name": "routine_runs_linked_issue_idx", + "columns": [ + { + "expression": "linked_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_trigger_idempotency_idx": { + "name": "routine_runs_trigger_idempotency_idx", + "columns": [ + { + "expression": "trigger_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_runs_company_id_companies_id_fk": { + "name": "routine_runs_company_id_companies_id_fk", + "tableFrom": "routine_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_runs_routine_id_routines_id_fk": { + "name": "routine_runs_routine_id_routines_id_fk", + "tableFrom": "routine_runs", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_runs_trigger_id_routine_triggers_id_fk": { + "name": "routine_runs_trigger_id_routine_triggers_id_fk", + "tableFrom": "routine_runs", + "tableTo": "routine_triggers", + "columnsFrom": [ + "trigger_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_runs_routine_revision_id_routine_revisions_id_fk": { + "name": "routine_runs_routine_revision_id_routine_revisions_id_fk", + "tableFrom": "routine_runs", + "tableTo": "routine_revisions", + "columnsFrom": [ + "routine_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_runs_linked_issue_id_issues_id_fk": { + "name": "routine_runs_linked_issue_id_issues_id_fk", + "tableFrom": "routine_runs", + "tableTo": "issues", + "columnsFrom": [ + "linked_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_triggers": { + "name": "routine_triggers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_fired_at": { + "name": "last_fired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "public_id": { + "name": "public_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "signing_mode": { + "name": "signing_mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "replay_window_sec": { + "name": "replay_window_sec", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_rotated_at": { + "name": "last_rotated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_result": { + "name": "last_result", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routine_triggers_company_routine_idx": { + "name": "routine_triggers_company_routine_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_company_kind_idx": { + "name": "routine_triggers_company_kind_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_next_run_idx": { + "name": "routine_triggers_next_run_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_public_id_idx": { + "name": "routine_triggers_public_id_idx", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_public_id_uq": { + "name": "routine_triggers_public_id_uq", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_triggers_company_id_companies_id_fk": { + "name": "routine_triggers_company_id_companies_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_triggers_routine_id_routines_id_fk": { + "name": "routine_triggers_routine_id_routines_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_triggers_secret_id_company_secrets_id_fk": { + "name": "routine_triggers_secret_id_company_secrets_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_triggers_created_by_agent_id_agents_id_fk": { + "name": "routine_triggers_created_by_agent_id_agents_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_triggers_updated_by_agent_id_agents_id_fk": { + "name": "routine_triggers_updated_by_agent_id_agents_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routines": { + "name": "routines", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "folder_id": { + "name": "folder_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_issue_id": { + "name": "parent_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_agent_id": { + "name": "assignee_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "concurrency_policy": { + "name": "concurrency_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'coalesce_if_active'" + }, + "catch_up_policy": { + "name": "catch_up_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'skip_missed'" + }, + "activity_gate_policy": { + "name": "activity_gate_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'always'" + }, + "activity_gate_scope": { + "name": "activity_gate_scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company'" + }, + "origin_kind": { + "name": "origin_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "origin_id": { + "name": "origin_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "variables": { + "name": "variables", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "env": { + "name": "env", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "latest_revision_id": { + "name": "latest_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "latest_revision_number": { + "name": "latest_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_triggered_at": { + "name": "last_triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_enqueued_at": { + "name": "last_enqueued_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": { + "routines_company_status_idx": { + "name": "routines_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_assignee_idx": { + "name": "routines_company_assignee_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_project_idx": { + "name": "routines_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_folder_idx": { + "name": "routines_company_folder_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_responsible_user_idx": { + "name": "routines_company_responsible_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "responsible_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_origin_idx": { + "name": "routines_company_origin_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routines_company_id_companies_id_fk": { + "name": "routines_company_id_companies_id_fk", + "tableFrom": "routines", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routines_project_id_projects_id_fk": { + "name": "routines_project_id_projects_id_fk", + "tableFrom": "routines", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routines_folder_id_folders_id_fk": { + "name": "routines_folder_id_folders_id_fk", + "tableFrom": "routines", + "tableTo": "folders", + "columnsFrom": [ + "folder_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_goal_id_goals_id_fk": { + "name": "routines_goal_id_goals_id_fk", + "tableFrom": "routines", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_parent_issue_id_issues_id_fk": { + "name": "routines_parent_issue_id_issues_id_fk", + "tableFrom": "routines", + "tableTo": "issues", + "columnsFrom": [ + "parent_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_assignee_agent_id_agents_id_fk": { + "name": "routines_assignee_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "assignee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "routines_created_by_agent_id_agents_id_fk": { + "name": "routines_created_by_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_updated_by_agent_id_agents_id_fk": { + "name": "routines_updated_by_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.run_identity_contexts": { + "name": "run_identity_contexts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "message_id": { + "name": "message_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_context_id": { + "name": "parent_context_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cause": { + "name": "cause", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'accepted'" + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "github": { + "name": "github", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "run_identity_contexts_run_revision_idx": { + "name": "run_identity_contexts_run_revision_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "run_identity_contexts_run_correlation_idx": { + "name": "run_identity_contexts_run_correlation_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "correlation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "run_identity_contexts_company_run_idx": { + "name": "run_identity_contexts_company_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "run_identity_contexts_company_id_companies_id_fk": { + "name": "run_identity_contexts_company_id_companies_id_fk", + "tableFrom": "run_identity_contexts", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.secret_access_events": { + "name": "secret_access_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_secret_definition_id": { + "name": "user_secret_definition_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "secret_scope": { + "name": "secret_scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company'" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_owner_user_id": { + "name": "credential_owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_subject_type": { + "name": "credential_subject_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_subject_id": { + "name": "credential_subject_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "consumer_type": { + "name": "consumer_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "consumer_id": { + "name": "consumer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config_path": { + "name": "config_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "secret_access_events_company_created_idx": { + "name": "secret_access_events_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secret_access_events_secret_created_idx": { + "name": "secret_access_events_secret_created_idx", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secret_access_events_user_definition_created_idx": { + "name": "secret_access_events_user_definition_created_idx", + "columns": [ + { + "expression": "user_secret_definition_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secret_access_events_company_credential_owner_idx": { + "name": "secret_access_events_company_credential_owner_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "credential_owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secret_access_events_consumer_idx": { + "name": "secret_access_events_consumer_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "consumer_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "consumer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secret_access_events_run_idx": { + "name": "secret_access_events_run_idx", + "columns": [ + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "secret_access_events_company_id_companies_id_fk": { + "name": "secret_access_events_company_id_companies_id_fk", + "tableFrom": "secret_access_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "secret_access_events_secret_id_company_secrets_id_fk": { + "name": "secret_access_events_secret_id_company_secrets_id_fk", + "tableFrom": "secret_access_events", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "secret_access_events_user_secret_definition_id_user_secret_definitions_id_fk": { + "name": "secret_access_events_user_secret_definition_id_user_secret_definitions_id_fk", + "tableFrom": "secret_access_events", + "tableTo": "user_secret_definitions", + "columnsFrom": [ + "user_secret_definition_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "secret_access_events_issue_id_issues_id_fk": { + "name": "secret_access_events_issue_id_issues_id_fk", + "tableFrom": "secret_access_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "secret_access_events_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "secret_access_events_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "secret_access_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "secret_access_events_plugin_id_plugins_id_fk": { + "name": "secret_access_events_plugin_id_plugins_id_fk", + "tableFrom": "secret_access_events", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.smoke_run_steps": { + "name": "smoke_run_steps", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scenario_step": { + "name": "scenario_step", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "detail": { + "name": "detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "screenshot_artifact_ref": { + "name": "screenshot_artifact_ref", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "smoke_run_steps_company_run_idx": { + "name": "smoke_run_steps_company_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "smoke_run_steps_company_path_idx": { + "name": "smoke_run_steps_company_path_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "smoke_run_steps_company_id_companies_id_fk": { + "name": "smoke_run_steps_company_id_companies_id_fk", + "tableFrom": "smoke_run_steps", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "smoke_run_steps_run_id_smoke_runs_id_fk": { + "name": "smoke_run_steps_run_id_smoke_runs_id_fk", + "tableFrom": "smoke_run_steps", + "tableTo": "smoke_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.smoke_runs": { + "name": "smoke_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_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": "'running'" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "summary": { + "name": "summary", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "smoke_runs_company_started_idx": { + "name": "smoke_runs_company_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "smoke_runs_company_status_idx": { + "name": "smoke_runs_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "smoke_runs_company_id_companies_id_fk": { + "name": "smoke_runs_company_id_companies_id_fk", + "tableFrom": "smoke_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.status_card_updates": { + "name": "status_card_updates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "card_id": { + "name": "card_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "generation_issue_id": { + "name": "generation_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "changes": { + "name": "changes", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "input_tokens": { + "name": "input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "output_tokens": { + "name": "output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cost_cents": { + "name": "cost_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "query_version": { + "name": "query_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "change_summary": { + "name": "change_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "status_card_updates_card_started_idx": { + "name": "status_card_updates_card_started_idx", + "columns": [ + { + "expression": "card_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "status_card_updates_generation_issue_idx": { + "name": "status_card_updates_generation_issue_idx", + "columns": [ + { + "expression": "generation_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "status_card_updates_card_id_status_cards_id_fk": { + "name": "status_card_updates_card_id_status_cards_id_fk", + "tableFrom": "status_card_updates", + "tableTo": "status_cards", + "columnsFrom": [ + "card_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "status_card_updates_generation_issue_id_issues_id_fk": { + "name": "status_card_updates_generation_issue_id_issues_id_fk", + "tableFrom": "status_card_updates", + "tableTo": "issues", + "columnsFrom": [ + "generation_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "status_card_updates_run_id_heartbeat_runs_id_fk": { + "name": "status_card_updates_run_id_heartbeat_runs_id_fk", + "tableFrom": "status_card_updates", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.status_cards": { + "name": "status_cards", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title_pinned": { + "name": "title_pinned", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "interest_prompt": { + "name": "interest_prompt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "queries": { + "name": "queries", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "query_version": { + "name": "query_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "query_compiled_at": { + "name": "query_compiled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "query_compiled_by_agent_id": { + "name": "query_compiled_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "refresh_policy": { + "name": "refresh_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'compiling'" + }, + "pending_change_count": { + "name": "pending_change_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "pending_change_hash": { + "name": "pending_change_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_change_at": { + "name": "last_change_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "fingerprint": { + "name": "fingerprint", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "fingerprint_at": { + "name": "fingerprint_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "mentioned_issue_ids": { + "name": "mentioned_issue_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_update_run_kind": { + "name": "last_update_run_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_generated_at": { + "name": "last_generated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_model": { + "name": "last_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "generating_issue_id": { + "name": "generating_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_eval_at": { + "name": "next_eval_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "archived_by_user_id": { + "name": "archived_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_by_agent_id": { + "name": "archived_by_agent_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": { + "status_cards_company_archived_idx": { + "name": "status_cards_company_archived_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "status_cards_company_next_eval_idx": { + "name": "status_cards_company_next_eval_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_eval_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "status_cards_company_id_companies_id_fk": { + "name": "status_cards_company_id_companies_id_fk", + "tableFrom": "status_cards", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "status_cards_created_by_agent_id_agents_id_fk": { + "name": "status_cards_created_by_agent_id_agents_id_fk", + "tableFrom": "status_cards", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "status_cards_query_compiled_by_agent_id_agents_id_fk": { + "name": "status_cards_query_compiled_by_agent_id_agents_id_fk", + "tableFrom": "status_cards", + "tableTo": "agents", + "columnsFrom": [ + "query_compiled_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "status_cards_agent_id_agents_id_fk": { + "name": "status_cards_agent_id_agents_id_fk", + "tableFrom": "status_cards", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "status_cards_document_id_documents_id_fk": { + "name": "status_cards_document_id_documents_id_fk", + "tableFrom": "status_cards", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "status_cards_generating_issue_id_issues_id_fk": { + "name": "status_cards_generating_issue_id_issues_id_fk", + "tableFrom": "status_cards", + "tableTo": "issues", + "columnsFrom": [ + "generating_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "status_cards_archived_by_agent_id_agents_id_fk": { + "name": "status_cards_archived_by_agent_id_agents_id_fk", + "tableFrom": "status_cards", + "tableTo": "agents", + "columnsFrom": [ + "archived_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.status_decision_effects": { + "name": "status_decision_effects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "decision_id": { + "name": "decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "ordinal": { + "name": "ordinal", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "effect_kind": { + "name": "effect_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "delivery_state": { + "name": "delivery_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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": { + "status_decision_effects_decision_ordinal_uq": { + "name": "status_decision_effects_decision_ordinal_uq", + "columns": [ + { + "expression": "decision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "ordinal", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "status_decision_effects_company_idempotency_uq": { + "name": "status_decision_effects_company_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "status_decision_effects_company_id_companies_id_fk": { + "name": "status_decision_effects_company_id_companies_id_fk", + "tableFrom": "status_decision_effects", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "status_decision_effects_issue_company_fk": { + "name": "status_decision_effects_issue_company_fk", + "tableFrom": "status_decision_effects", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "status_decision_effects_decision_owner_fk": { + "name": "status_decision_effects_decision_owner_fk", + "tableFrom": "status_decision_effects", + "tableTo": "status_decisions", + "columnsFrom": [ + "company_id", + "issue_id", + "decision_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.status_decisions": { + "name": "status_decisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "assessment_id": { + "name": "assessment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "decision_version": { + "name": "decision_version", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "policy_version": { + "name": "policy_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_status": { + "name": "from_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "to_status": { + "name": "to_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason_code": { + "name": "reason_code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "decision_json": { + "name": "decision_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "decision_digest": { + "name": "decision_digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "application_state": { + "name": "application_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'proposed'" + }, + "supersedes_decision_id": { + "name": "supersedes_decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "applied_at": { + "name": "applied_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": { + "status_decisions_company_issue_version_uq": { + "name": "status_decisions_company_issue_version_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "decision_version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "status_decisions_company_assessment_uq": { + "name": "status_decisions_company_assessment_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assessment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "status_decisions_company_issue_digest_uq": { + "name": "status_decisions_company_issue_digest_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "decision_digest", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "status_decisions_company_id_companies_id_fk": { + "name": "status_decisions_company_id_companies_id_fk", + "tableFrom": "status_decisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "status_decisions_issue_company_fk": { + "name": "status_decisions_issue_company_fk", + "tableFrom": "status_decisions", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "status_decisions_assessment_owner_fk": { + "name": "status_decisions_assessment_owner_fk", + "tableFrom": "status_decisions", + "tableTo": "work_assessments", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "assessment_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "run_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "status_decisions_supersedes_owner_fk": { + "name": "status_decisions_supersedes_owner_fk", + "tableFrom": "status_decisions", + "tableTo": "status_decisions", + "columnsFrom": [ + "company_id", + "issue_id", + "supersedes_decision_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "status_decisions_company_issue_id_uq": { + "name": "status_decisions_company_issue_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "issue_id", + "id" + ] + }, + "status_decisions_company_issue_run_assessment_id_uq": { + "name": "status_decisions_company_issue_run_assessment_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "issue_id", + "run_id", + "assessment_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.summary_slots": { + "name": "summary_slots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_kind": { + "name": "scope_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "slot_key": { + "name": "slot_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "generating_issue_id": { + "name": "generating_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_generated_at": { + "name": "last_generated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_generated_by_agent_id": { + "name": "last_generated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_model": { + "name": "last_model", + "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": { + "summary_slots_document_uq": { + "name": "summary_slots_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "summary_slots_company_scope_idx": { + "name": "summary_slots_company_scope_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "summary_slots_company_generating_issue_idx": { + "name": "summary_slots_company_generating_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "generating_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "summary_slots_company_updated_idx": { + "name": "summary_slots_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "summary_slots_company_id_companies_id_fk": { + "name": "summary_slots_company_id_companies_id_fk", + "tableFrom": "summary_slots", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "summary_slots_document_id_documents_id_fk": { + "name": "summary_slots_document_id_documents_id_fk", + "tableFrom": "summary_slots", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "summary_slots_generating_issue_id_issues_id_fk": { + "name": "summary_slots_generating_issue_id_issues_id_fk", + "tableFrom": "summary_slots", + "tableTo": "issues", + "columnsFrom": [ + "generating_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "summary_slots_last_generated_by_agent_id_agents_id_fk": { + "name": "summary_slots_last_generated_by_agent_id_agents_id_fk", + "tableFrom": "summary_slots", + "tableTo": "agents", + "columnsFrom": [ + "last_generated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "summary_slots_company_scope_slot_uq": { + "name": "summary_slots_company_scope_slot_uq", + "nullsNotDistinct": true, + "columns": [ + "company_id", + "scope_kind", + "scope_id", + "slot_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_access_audit_events": { + "name": "tool_access_audit_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "gateway_id": { + "name": "gateway_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_token_id": { + "name": "gateway_token_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_public_id": { + "name": "gateway_public_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "catalog_entry_id": { + "name": "catalog_entry_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason_code": { + "name": "reason_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "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": { + "tool_access_audit_company_created_idx": { + "name": "tool_access_audit_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_access_audit_connection_idx": { + "name": "tool_access_audit_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_access_audit_gateway_idx": { + "name": "tool_access_audit_gateway_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "gateway_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_access_audit_events_company_id_companies_id_fk": { + "name": "tool_access_audit_events_company_id_companies_id_fk", + "tableFrom": "tool_access_audit_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_access_audit_events_gateway_id_tool_mcp_gateways_id_fk": { + "name": "tool_access_audit_events_gateway_id_tool_mcp_gateways_id_fk", + "tableFrom": "tool_access_audit_events", + "tableTo": "tool_mcp_gateways", + "columnsFrom": [ + "gateway_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_access_audit_events_gateway_token_id_tool_mcp_gateway_tokens_id_fk": { + "name": "tool_access_audit_events_gateway_token_id_tool_mcp_gateway_tokens_id_fk", + "tableFrom": "tool_access_audit_events", + "tableTo": "tool_mcp_gateway_tokens", + "columnsFrom": [ + "gateway_token_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_access_audit_events_connection_id_tool_connections_id_fk": { + "name": "tool_access_audit_events_connection_id_tool_connections_id_fk", + "tableFrom": "tool_access_audit_events", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_access_audit_events_catalog_entry_id_tool_catalog_entries_id_fk": { + "name": "tool_access_audit_events_catalog_entry_id_tool_catalog_entries_id_fk", + "tableFrom": "tool_access_audit_events", + "tableTo": "tool_catalog_entries", + "columnsFrom": [ + "catalog_entry_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_action_deliveries": { + "name": "tool_action_deliveries", + "schema": "", + "columns": { + "action_request_id": { + "name": "action_request_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "interaction_id": { + "name": "interaction_id", + "type": "uuid", + "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()" + } + }, + "indexes": { + "tool_action_deliveries_pending_idx": { + "name": "tool_action_deliveries_pending_idx", + "columns": [ + { + "expression": "delivered_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_action_deliveries_action_request_id_tool_action_requests_id_fk": { + "name": "tool_action_deliveries_action_request_id_tool_action_requests_id_fk", + "tableFrom": "tool_action_deliveries", + "tableTo": "tool_action_requests", + "columnsFrom": [ + "action_request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_action_deliveries_company_id_companies_id_fk": { + "name": "tool_action_deliveries_company_id_companies_id_fk", + "tableFrom": "tool_action_deliveries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_action_deliveries_issue_id_issues_id_fk": { + "name": "tool_action_deliveries_issue_id_issues_id_fk", + "tableFrom": "tool_action_deliveries", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_action_deliveries_interaction_id_issue_thread_interactions_id_fk": { + "name": "tool_action_deliveries_interaction_id_issue_thread_interactions_id_fk", + "tableFrom": "tool_action_deliveries", + "tableTo": "issue_thread_interactions", + "columnsFrom": [ + "interaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_action_requests": { + "name": "tool_action_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "invocation_id": { + "name": "invocation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "interaction_id": { + "name": "interaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "canonical_arguments_hash": { + "name": "canonical_arguments_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "canonical_arguments_summary": { + "name": "canonical_arguments_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "signed_arguments": { + "name": "signed_arguments", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "preview_markdown": { + "name": "preview_markdown", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_by_agent_id": { + "name": "requested_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_by_user_id": { + "name": "requested_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_by_agent_id": { + "name": "resolved_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_by_user_id": { + "name": "resolved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_by_agent_id": { + "name": "decided_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "decided_by_user_id": { + "name": "decided_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_at": { + "name": "decided_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_action_requests_company_status_idx": { + "name": "tool_action_requests_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_action_requests_invocation_idx": { + "name": "tool_action_requests_invocation_idx", + "columns": [ + { + "expression": "invocation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_action_requests_issue_idx": { + "name": "tool_action_requests_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_action_requests_company_id_companies_id_fk": { + "name": "tool_action_requests_company_id_companies_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_action_requests_invocation_id_tool_invocations_id_fk": { + "name": "tool_action_requests_invocation_id_tool_invocations_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "tool_invocations", + "columnsFrom": [ + "invocation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_action_requests_issue_id_issues_id_fk": { + "name": "tool_action_requests_issue_id_issues_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_action_requests_interaction_id_issue_thread_interactions_id_fk": { + "name": "tool_action_requests_interaction_id_issue_thread_interactions_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "issue_thread_interactions", + "columnsFrom": [ + "interaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_action_requests_approval_id_approvals_id_fk": { + "name": "tool_action_requests_approval_id_approvals_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_action_requests_requested_by_agent_id_agents_id_fk": { + "name": "tool_action_requests_requested_by_agent_id_agents_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "agents", + "columnsFrom": [ + "requested_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_action_requests_resolved_by_agent_id_agents_id_fk": { + "name": "tool_action_requests_resolved_by_agent_id_agents_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "agents", + "columnsFrom": [ + "resolved_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_action_requests_decided_by_agent_id_agents_id_fk": { + "name": "tool_action_requests_decided_by_agent_id_agents_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "agents", + "columnsFrom": [ + "decided_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_applications": { + "name": "tool_applications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "application_key": { + "name": "application_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "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": { + "tool_applications_company_idx": { + "name": "tool_applications_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_applications_company_status_idx": { + "name": "tool_applications_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_applications_company_name_uq": { + "name": "tool_applications_company_name_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_applications_company_key_uq": { + "name": "tool_applications_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "application_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_applications_company_id_companies_id_fk": { + "name": "tool_applications_company_id_companies_id_fk", + "tableFrom": "tool_applications", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_applications_plugin_id_plugins_id_fk": { + "name": "tool_applications_plugin_id_plugins_id_fk", + "tableFrom": "tool_applications", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_applications_owner_agent_id_agents_id_fk": { + "name": "tool_applications_owner_agent_id_agents_id_fk", + "tableFrom": "tool_applications", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_call_events": { + "name": "tool_call_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_id": { + "name": "gateway_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_token_id": { + "name": "gateway_token_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_public_id": { + "name": "gateway_public_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_subject_type": { + "name": "client_subject_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_subject_id": { + "name": "client_subject_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mcp_session_id": { + "name": "mcp_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "catalog_entry_id": { + "name": "catalog_entry_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "invocation_id": { + "name": "invocation_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "action_request_id": { + "name": "action_request_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "runtime_slot_id": { + "name": "runtime_slot_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decision": { + "name": "decision", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "matched_policy_ids": { + "name": "matched_policy_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "reason_code": { + "name": "reason_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "policy_explanation": { + "name": "policy_explanation", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "credential_scope_summary": { + "name": "credential_scope_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "header_policy_summary": { + "name": "header_policy_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "latency_ms": { + "name": "latency_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "arguments_summary": { + "name": "arguments_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "request_hash": { + "name": "request_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_summary": { + "name": "request_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "result_hash": { + "name": "result_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_summary": { + "name": "result_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "result_size_bytes": { + "name": "result_size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "redaction_plan": { + "name": "redaction_plan", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "rate_limit_state": { + "name": "rate_limit_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_call_events_company_created_idx": { + "name": "tool_call_events_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_call_events_run_idx": { + "name": "tool_call_events_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_call_events_issue_idx": { + "name": "tool_call_events_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_call_events_invocation_idx": { + "name": "tool_call_events_invocation_idx", + "columns": [ + { + "expression": "invocation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_call_events_gateway_idx": { + "name": "tool_call_events_gateway_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "gateway_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_call_events_company_id_companies_id_fk": { + "name": "tool_call_events_company_id_companies_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_call_events_agent_id_agents_id_fk": { + "name": "tool_call_events_agent_id_agents_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_run_id_heartbeat_runs_id_fk": { + "name": "tool_call_events_run_id_heartbeat_runs_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_issue_id_issues_id_fk": { + "name": "tool_call_events_issue_id_issues_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_gateway_id_tool_mcp_gateways_id_fk": { + "name": "tool_call_events_gateway_id_tool_mcp_gateways_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_mcp_gateways", + "columnsFrom": [ + "gateway_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_gateway_token_id_tool_mcp_gateway_tokens_id_fk": { + "name": "tool_call_events_gateway_token_id_tool_mcp_gateway_tokens_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_mcp_gateway_tokens", + "columnsFrom": [ + "gateway_token_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_application_id_tool_applications_id_fk": { + "name": "tool_call_events_application_id_tool_applications_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_connection_id_tool_connections_id_fk": { + "name": "tool_call_events_connection_id_tool_connections_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_catalog_entry_id_tool_catalog_entries_id_fk": { + "name": "tool_call_events_catalog_entry_id_tool_catalog_entries_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_catalog_entries", + "columnsFrom": [ + "catalog_entry_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_invocation_id_tool_invocations_id_fk": { + "name": "tool_call_events_invocation_id_tool_invocations_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_invocations", + "columnsFrom": [ + "invocation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_action_request_id_tool_action_requests_id_fk": { + "name": "tool_call_events_action_request_id_tool_action_requests_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_action_requests", + "columnsFrom": [ + "action_request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_runtime_slot_id_tool_runtime_slots_id_fk": { + "name": "tool_call_events_runtime_slot_id_tool_runtime_slots_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_runtime_slots", + "columnsFrom": [ + "runtime_slot_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_catalog_entries": { + "name": "tool_catalog_entries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "entry_kind": { + "name": "entry_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'tool'" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "input_schema": { + "name": "input_schema", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "output_schema": { + "name": "output_schema", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "annotations": { + "name": "annotations", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "risk_level": { + "name": "risk_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'read'" + }, + "is_read_only": { + "name": "is_read_only", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_write": { + "name": "is_write", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_destructive": { + "name": "is_destructive", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "version_hash": { + "name": "version_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schema_hash": { + "name": "schema_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "first_seen_at": { + "name": "first_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "reviewed_at": { + "name": "reviewed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "reviewed_by_agent_id": { + "name": "reviewed_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "reviewed_by_user_id": { + "name": "reviewed_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "quarantined_at": { + "name": "quarantined_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "quarantine_reason": { + "name": "quarantine_reason", + "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": { + "tool_catalog_entries_company_idx": { + "name": "tool_catalog_entries_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_catalog_entries_application_idx": { + "name": "tool_catalog_entries_application_idx", + "columns": [ + { + "expression": "application_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_catalog_entries_connection_idx": { + "name": "tool_catalog_entries_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_catalog_entries_company_status_idx": { + "name": "tool_catalog_entries_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_catalog_entries_connection_name_uq": { + "name": "tool_catalog_entries_connection_name_uq", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_catalog_entries_company_id_companies_id_fk": { + "name": "tool_catalog_entries_company_id_companies_id_fk", + "tableFrom": "tool_catalog_entries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_catalog_entries_application_id_tool_applications_id_fk": { + "name": "tool_catalog_entries_application_id_tool_applications_id_fk", + "tableFrom": "tool_catalog_entries", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_catalog_entries_connection_id_tool_connections_id_fk": { + "name": "tool_catalog_entries_connection_id_tool_connections_id_fk", + "tableFrom": "tool_catalog_entries", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_catalog_entries_reviewed_by_agent_id_agents_id_fk": { + "name": "tool_catalog_entries_reviewed_by_agent_id_agents_id_fk", + "tableFrom": "tool_catalog_entries", + "tableTo": "agents", + "columnsFrom": [ + "reviewed_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_connection_installs": { + "name": "tool_connection_installs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "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 + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_connection_installs_company_target_idx": { + "name": "tool_connection_installs_company_target_idx", + "columns": [ + { + "expression": "company_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": {} + }, + "tool_connection_installs_connection_idx": { + "name": "tool_connection_installs_connection_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_connection_installs_target_uq": { + "name": "tool_connection_installs_target_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_connection_installs_company_id_companies_id_fk": { + "name": "tool_connection_installs_company_id_companies_id_fk", + "tableFrom": "tool_connection_installs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_connection_installs_connection_id_tool_connections_id_fk": { + "name": "tool_connection_installs_connection_id_tool_connections_id_fk", + "tableFrom": "tool_connection_installs", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_connection_installs_created_by_agent_id_agents_id_fk": { + "name": "tool_connection_installs_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_connection_installs", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "tool_connection_installs_target_type_check": { + "name": "tool_connection_installs_target_type_check", + "value": "\"tool_connection_installs\".\"target_type\" in ('company', 'agent')" + } + }, + "isRLSEnabled": false + }, + "public.tool_connections": { + "name": "tool_connections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "uid": { + "name": "uid", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connection_kind": { + "name": "connection_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'managed'" + }, + "connection_purpose": { + "name": "connection_purpose", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'tool'" + }, + "ownership": { + "name": "ownership", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'customer'" + }, + "transport": { + "name": "transport", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "auth_kind": { + "name": "auth_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "credential_source": { + "name": "credential_source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip_vault'" + }, + "external_credential": { + "name": "external_credential", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "credential_policy": { + "name": "credential_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'shared'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "transport_config": { + "name": "transport_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "credential_refs": { + "name": "credential_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "credential_secret_refs": { + "name": "credential_secret_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unchecked'" + }, + "health_message": { + "name": "health_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "health_checked_at": { + "name": "health_checked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_health_at": { + "name": "last_health_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_catalog_refresh_at": { + "name": "last_catalog_refresh_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_connections_company_idx": { + "name": "tool_connections_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_connections_application_idx": { + "name": "tool_connections_application_idx", + "columns": [ + { + "expression": "application_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_connections_company_enabled_idx": { + "name": "tool_connections_company_enabled_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_connections_company_uid_uq": { + "name": "tool_connections_company_uid_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "uid", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_connections_company_id_companies_id_fk": { + "name": "tool_connections_company_id_companies_id_fk", + "tableFrom": "tool_connections", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_connections_application_id_tool_applications_id_fk": { + "name": "tool_connections_application_id_tool_applications_id_fk", + "tableFrom": "tool_connections", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tool_connections_created_by_agent_id_agents_id_fk": { + "name": "tool_connections_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_connections", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "tool_connections_company_id_uq": { + "name": "tool_connections_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "tool_connections_ownership_check": { + "name": "tool_connections_ownership_check", + "value": "\"tool_connections\".\"ownership\" in ('platform_shared', 'platform_provisioned', 'customer', 'dcr')" + }, + "tool_connections_transport_check": { + "name": "tool_connections_transport_check", + "value": "\"tool_connections\".\"transport\" in ('mcp_remote', 'rest_api', 'local_stdio', 'chat_sdk', 'runtime_auth')" + }, + "tool_connections_purpose_check": { + "name": "tool_connections_purpose_check", + "value": "\"tool_connections\".\"connection_purpose\" in ('tool', 'channel', 'ai')" + }, + "tool_connections_channel_transport_check": { + "name": "tool_connections_channel_transport_check", + "value": "(\n (\"tool_connections\".\"connection_purpose\" = 'tool' and \"tool_connections\".\"transport\" not in ('chat_sdk', 'runtime_auth'))\n or\n (\"tool_connections\".\"connection_purpose\" = 'channel' and (\"tool_connections\".\"transport\" = 'chat_sdk' or (\"tool_connections\".\"transport\" = 'rest_api' and \"tool_connections\".\"config\"->>'provider' = 'agentmail')))\n or\n (\"tool_connections\".\"connection_purpose\" = 'ai' and \"tool_connections\".\"transport\" = 'runtime_auth')\n )" + }, + "tool_connections_auth_kind_check": { + "name": "tool_connections_auth_kind_check", + "value": "\"tool_connections\".\"auth_kind\" in ('oauth', 'api_key', 'none')" + }, + "tool_connections_credential_source_check": { + "name": "tool_connections_credential_source_check", + "value": "\"tool_connections\".\"credential_source\" in ('paperclip_vault', 'vercel_connect')" + }, + "tool_connections_credential_source_one_of_check": { + "name": "tool_connections_credential_source_one_of_check", + "value": "(\n (\"tool_connections\".\"credential_source\" = 'paperclip_vault' and \"tool_connections\".\"external_credential\" is null)\n or\n (\"tool_connections\".\"credential_source\" = 'vercel_connect' and \"tool_connections\".\"external_credential\" is not null and jsonb_array_length(\"tool_connections\".\"credential_refs\") = 0 and jsonb_array_length(\"tool_connections\".\"credential_secret_refs\") = 0)\n )" + }, + "tool_connections_credential_policy_check": { + "name": "tool_connections_credential_policy_check", + "value": "\"tool_connections\".\"credential_policy\" in ('shared', 'per_user', 'per_user_with_fallback', 'per_agent')" + } + }, + "isRLSEnabled": false + }, + "public.tool_gateway_rate_limit_counters": { + "name": "tool_gateway_rate_limit_counters", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "counter_key": { + "name": "counter_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_start_at": { + "name": "window_start_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "window_ms": { + "name": "window_ms", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "limit": { + "name": "limit", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "reset_at": { + "name": "reset_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": { + "tool_gateway_rate_limit_counters_company_idx": { + "name": "tool_gateway_rate_limit_counters_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_gateway_rate_limit_counters_window_uq": { + "name": "tool_gateway_rate_limit_counters_window_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "counter_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_start_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_gateway_rate_limit_counters_company_id_companies_id_fk": { + "name": "tool_gateway_rate_limit_counters_company_id_companies_id_fk", + "tableFrom": "tool_gateway_rate_limit_counters", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_gateway_sessions": { + "name": "tool_gateway_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_id": { + "name": "gateway_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_token_id": { + "name": "gateway_token_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_public_id": { + "name": "gateway_public_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_subject_type": { + "name": "client_subject_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_subject_id": { + "name": "client_subject_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mcp_session_id": { + "name": "mcp_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "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 + }, + "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": { + "tool_gateway_sessions_token_hash_uq": { + "name": "tool_gateway_sessions_token_hash_uq", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_gateway_sessions_company_agent_idx": { + "name": "tool_gateway_sessions_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_gateway_sessions_company_expires_idx": { + "name": "tool_gateway_sessions_company_expires_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_gateway_sessions_run_idx": { + "name": "tool_gateway_sessions_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_gateway_sessions_issue_idx": { + "name": "tool_gateway_sessions_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_gateway_sessions_gateway_idx": { + "name": "tool_gateway_sessions_gateway_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "gateway_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_gateway_sessions_company_id_companies_id_fk": { + "name": "tool_gateway_sessions_company_id_companies_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_gateway_sessions_agent_id_agents_id_fk": { + "name": "tool_gateway_sessions_agent_id_agents_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_gateway_sessions_run_id_heartbeat_runs_id_fk": { + "name": "tool_gateway_sessions_run_id_heartbeat_runs_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_gateway_sessions_issue_id_issues_id_fk": { + "name": "tool_gateway_sessions_issue_id_issues_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_gateway_sessions_project_id_projects_id_fk": { + "name": "tool_gateway_sessions_project_id_projects_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_gateway_sessions_gateway_id_tool_mcp_gateways_id_fk": { + "name": "tool_gateway_sessions_gateway_id_tool_mcp_gateways_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "tool_mcp_gateways", + "columnsFrom": [ + "gateway_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_gateway_sessions_gateway_token_id_tool_mcp_gateway_tokens_id_fk": { + "name": "tool_gateway_sessions_gateway_token_id_tool_mcp_gateway_tokens_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "tool_mcp_gateway_tokens", + "columnsFrom": [ + "gateway_token_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_invocations": { + "name": "tool_invocations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_id": { + "name": "gateway_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_token_id": { + "name": "gateway_token_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_public_id": { + "name": "gateway_public_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_subject_type": { + "name": "client_subject_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_subject_id": { + "name": "client_subject_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mcp_session_id": { + "name": "mcp_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "catalog_entry_id": { + "name": "catalog_entry_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "catalog_version_hash": { + "name": "catalog_version_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "catalog_schema_hash": { + "name": "catalog_schema_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_type": { + "name": "provider_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "application_key": { + "name": "application_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "upstream_tool_name": { + "name": "upstream_tool_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "risk_level": { + "name": "risk_level", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "arguments_hash": { + "name": "arguments_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "arguments_summary": { + "name": "arguments_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "policy_decision": { + "name": "policy_decision", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "matched_policy_ids": { + "name": "matched_policy_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "policy_explanation": { + "name": "policy_explanation", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "credential_scope_summary": { + "name": "credential_scope_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "header_policy_summary": { + "name": "header_policy_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "approval_state": { + "name": "approval_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'not_required'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "upstream_request_id": { + "name": "upstream_request_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_hash": { + "name": "result_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_summary": { + "name": "result_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "result_size_bytes": { + "name": "result_size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "result_artifact_id": { + "name": "result_artifact_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "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": { + "tool_invocations_company_created_idx": { + "name": "tool_invocations_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_invocations_run_idx": { + "name": "tool_invocations_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_invocations_issue_idx": { + "name": "tool_invocations_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_invocations_gateway_idx": { + "name": "tool_invocations_gateway_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "gateway_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_invocations_company_idempotency_uq": { + "name": "tool_invocations_company_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_invocations_company_id_companies_id_fk": { + "name": "tool_invocations_company_id_companies_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_invocations_agent_id_agents_id_fk": { + "name": "tool_invocations_agent_id_agents_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_issue_id_issues_id_fk": { + "name": "tool_invocations_issue_id_issues_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_run_id_heartbeat_runs_id_fk": { + "name": "tool_invocations_run_id_heartbeat_runs_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_gateway_id_tool_mcp_gateways_id_fk": { + "name": "tool_invocations_gateway_id_tool_mcp_gateways_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "tool_mcp_gateways", + "columnsFrom": [ + "gateway_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_gateway_token_id_tool_mcp_gateway_tokens_id_fk": { + "name": "tool_invocations_gateway_token_id_tool_mcp_gateway_tokens_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "tool_mcp_gateway_tokens", + "columnsFrom": [ + "gateway_token_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_application_id_tool_applications_id_fk": { + "name": "tool_invocations_application_id_tool_applications_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_connection_id_tool_connections_id_fk": { + "name": "tool_invocations_connection_id_tool_connections_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_catalog_entry_id_tool_catalog_entries_id_fk": { + "name": "tool_invocations_catalog_entry_id_tool_catalog_entries_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "tool_catalog_entries", + "columnsFrom": [ + "catalog_entry_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_mcp_gateway_tokens": { + "name": "tool_mcp_gateway_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "gateway_id": { + "name": "gateway_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, + "default": "''" + }, + "subject_type": { + "name": "subject_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'gateway_client'" + }, + "subject_id": { + "name": "subject_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_label": { + "name": "client_label", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "owner_note": { + "name": "owner_note", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "allowed_actions": { + "name": "allowed_actions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[\"tools/list\",\"tools/call\"]'::jsonb" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expiry_override_reason": { + "name": "expiry_override_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expiry_override_by_user_id": { + "name": "expiry_override_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expiry_override_by_agent_id": { + "name": "expiry_override_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "expiry_override_at": { + "name": "expiry_override_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "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 + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_mcp_gateway_tokens_token_hash_uq": { + "name": "tool_mcp_gateway_tokens_token_hash_uq", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateway_tokens_gateway_idx": { + "name": "tool_mcp_gateway_tokens_gateway_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "gateway_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateway_tokens_subject_idx": { + "name": "tool_mcp_gateway_tokens_subject_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateway_tokens_company_expires_idx": { + "name": "tool_mcp_gateway_tokens_company_expires_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_mcp_gateway_tokens_company_id_companies_id_fk": { + "name": "tool_mcp_gateway_tokens_company_id_companies_id_fk", + "tableFrom": "tool_mcp_gateway_tokens", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_mcp_gateway_tokens_gateway_id_tool_mcp_gateways_id_fk": { + "name": "tool_mcp_gateway_tokens_gateway_id_tool_mcp_gateways_id_fk", + "tableFrom": "tool_mcp_gateway_tokens", + "tableTo": "tool_mcp_gateways", + "columnsFrom": [ + "gateway_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_mcp_gateway_tokens_expiry_override_by_agent_id_agents_id_fk": { + "name": "tool_mcp_gateway_tokens_expiry_override_by_agent_id_agents_id_fk", + "tableFrom": "tool_mcp_gateway_tokens", + "tableTo": "agents", + "columnsFrom": [ + "expiry_override_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_mcp_gateway_tokens_created_by_agent_id_agents_id_fk": { + "name": "tool_mcp_gateway_tokens_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_mcp_gateway_tokens", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_mcp_gateways": { + "name": "tool_mcp_gateways", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "gateway_public_id": { + "name": "gateway_public_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'gw_' || replace(gen_random_uuid()::text, '-', '')" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_slug": { + "name": "display_slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "default_profile_mode": { + "name": "default_profile_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'gateway_only'" + }, + "context_scope_type": { + "name": "context_scope_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "context_scope_id": { + "name": "context_scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approval_issue_id": { + "name": "approval_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "auth_config": { + "name": "auth_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"version\":1,\"bearer\":{\"enabled\":true,\"tokenPrefix\":\"pcgw\",\"defaultTtlSeconds\":7776000,\"requireFiniteExpiry\":true,\"longLivedTokenRequiresOverride\":true},\"oauth\":{\"enabled\":false,\"reservedFor\":\"v1_5\",\"dynamicClientRegistration\":false,\"authorizationCodePkce\":false}}'::jsonb" + }, + "header_policy": { + "name": "header_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"version\":1,\"callerPassthrough\":{\"enabled\":false,\"allowedHeaders\":[]},\"staticHeaders\":[],\"generatedMetadata\":{\"enabled\":false,\"allowedHeaders\":[]},\"responseHeaders\":{\"forwardMcpRequiredHeaders\":true,\"forwardSafeCacheHeaders\":true}}'::jsonb" + }, + "metadata_policy": { + "name": "metadata_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"version\":1,\"forwardCompanyId\":false,\"forwardGatewayId\":false,\"forwardProjectId\":false,\"forwardIssueId\":false,\"forwardAgentId\":false,\"forwardRunId\":false,\"forwardCorrelationId\":true}'::jsonb" + }, + "on_demand_tools_config": { + "name": "on_demand_tools_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"enabled\":false,\"searchToolName\":\"search_tools\",\"runToolName\":\"run_tool\"}'::jsonb" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "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": { + "tool_mcp_gateways_company_idx": { + "name": "tool_mcp_gateways_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateways_company_status_idx": { + "name": "tool_mcp_gateways_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateways_profile_idx": { + "name": "tool_mcp_gateways_profile_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateways_public_id_uq": { + "name": "tool_mcp_gateways_public_id_uq", + "columns": [ + { + "expression": "gateway_public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateways_company_slug_uq": { + "name": "tool_mcp_gateways_company_slug_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateways_company_name_uq": { + "name": "tool_mcp_gateways_company_name_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_mcp_gateways_company_id_companies_id_fk": { + "name": "tool_mcp_gateways_company_id_companies_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_mcp_gateways_profile_id_tool_profiles_id_fk": { + "name": "tool_mcp_gateways_profile_id_tool_profiles_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "tool_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "tool_mcp_gateways_agent_id_agents_id_fk": { + "name": "tool_mcp_gateways_agent_id_agents_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_mcp_gateways_project_id_projects_id_fk": { + "name": "tool_mcp_gateways_project_id_projects_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_mcp_gateways_issue_id_issues_id_fk": { + "name": "tool_mcp_gateways_issue_id_issues_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_mcp_gateways_approval_issue_id_issues_id_fk": { + "name": "tool_mcp_gateways_approval_issue_id_issues_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "issues", + "columnsFrom": [ + "approval_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_mcp_gateways_created_by_agent_id_agents_id_fk": { + "name": "tool_mcp_gateways_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_oauth_states": { + "name": "tool_oauth_states", + "schema": "", + "columns": { + "state": { + "name": "state", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "code_verifier": { + "name": "code_verifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_actor_type": { + "name": "created_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_actor_id": { + "name": "created_by_actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_session_id": { + "name": "created_by_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subject_user_id": { + "name": "subject_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subject_agent_id": { + "name": "subject_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_scopes": { + "name": "requested_scopes", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "return_to": { + "name": "return_to", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "interaction_id": { + "name": "interaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_oauth_states_company_idx": { + "name": "tool_oauth_states_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_oauth_states_connection_idx": { + "name": "tool_oauth_states_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_oauth_states_actor_idx": { + "name": "tool_oauth_states_actor_idx", + "columns": [ + { + "expression": "created_by_actor_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_by_actor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_oauth_states_subject_agent_idx": { + "name": "tool_oauth_states_subject_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_oauth_states_expires_at_idx": { + "name": "tool_oauth_states_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_oauth_states_company_id_companies_id_fk": { + "name": "tool_oauth_states_company_id_companies_id_fk", + "tableFrom": "tool_oauth_states", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_oauth_states_connection_id_tool_connections_id_fk": { + "name": "tool_oauth_states_connection_id_tool_connections_id_fk", + "tableFrom": "tool_oauth_states", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_oauth_states_subject_agent_id_agents_id_fk": { + "name": "tool_oauth_states_subject_agent_id_agents_id_fk", + "tableFrom": "tool_oauth_states", + "tableTo": "agents", + "columnsFrom": [ + "subject_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_policies": { + "name": "tool_policies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "policy_type": { + "name": "policy_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 100 + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "selectors": { + "name": "selectors", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "conditions": { + "name": "conditions", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_policies_company_enabled_idx": { + "name": "tool_policies_company_enabled_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_policies_company_type_idx": { + "name": "tool_policies_company_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "policy_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_policies_company_name_uq": { + "name": "tool_policies_company_name_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_policies_company_id_companies_id_fk": { + "name": "tool_policies_company_id_companies_id_fk", + "tableFrom": "tool_policies", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_policies_created_by_agent_id_agents_id_fk": { + "name": "tool_policies_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_policies", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_profile_bindings": { + "name": "tool_profile_bindings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "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 + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 100 + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_profile_bindings_company_target_idx": { + "name": "tool_profile_bindings_company_target_idx", + "columns": [ + { + "expression": "company_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": {} + }, + "tool_profile_bindings_target_profile_uq": { + "name": "tool_profile_bindings_target_profile_uq", + "columns": [ + { + "expression": "company_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" + }, + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_profile_bindings_company_id_companies_id_fk": { + "name": "tool_profile_bindings_company_id_companies_id_fk", + "tableFrom": "tool_profile_bindings", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_profile_bindings_profile_id_tool_profiles_id_fk": { + "name": "tool_profile_bindings_profile_id_tool_profiles_id_fk", + "tableFrom": "tool_profile_bindings", + "tableTo": "tool_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_profile_bindings_created_by_agent_id_agents_id_fk": { + "name": "tool_profile_bindings_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_profile_bindings", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_profile_entries": { + "name": "tool_profile_entries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "selector_type": { + "name": "selector_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "effect": { + "name": "effect", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'include'" + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "catalog_entry_id": { + "name": "catalog_entry_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "risk_level": { + "name": "risk_level", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "conditions": { + "name": "conditions", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_profile_entries_company_profile_idx": { + "name": "tool_profile_entries_company_profile_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_profile_entries_application_idx": { + "name": "tool_profile_entries_application_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "application_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_profile_entries_connection_idx": { + "name": "tool_profile_entries_connection_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_profile_entries_catalog_entry_idx": { + "name": "tool_profile_entries_catalog_entry_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "catalog_entry_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_profile_entries_company_id_companies_id_fk": { + "name": "tool_profile_entries_company_id_companies_id_fk", + "tableFrom": "tool_profile_entries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_profile_entries_profile_id_tool_profiles_id_fk": { + "name": "tool_profile_entries_profile_id_tool_profiles_id_fk", + "tableFrom": "tool_profile_entries", + "tableTo": "tool_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_profile_entries_application_id_tool_applications_id_fk": { + "name": "tool_profile_entries_application_id_tool_applications_id_fk", + "tableFrom": "tool_profile_entries", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_profile_entries_connection_id_tool_connections_id_fk": { + "name": "tool_profile_entries_connection_id_tool_connections_id_fk", + "tableFrom": "tool_profile_entries", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_profile_entries_catalog_entry_id_tool_catalog_entries_id_fk": { + "name": "tool_profile_entries_catalog_entry_id_tool_catalog_entries_id_fk", + "tableFrom": "tool_profile_entries", + "tableTo": "tool_catalog_entries", + "columnsFrom": [ + "catalog_entry_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_profiles": { + "name": "tool_profiles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "profile_key": { + "name": "profile_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "default_action": { + "name": "default_action", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'deny'" + }, + "new_tools_reviewed_at": { + "name": "new_tools_reviewed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "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()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_profiles_company_status_idx": { + "name": "tool_profiles_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_profiles_company_key_uq": { + "name": "tool_profiles_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "profile_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_profiles_company_name_uq": { + "name": "tool_profiles_company_name_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_profiles_company_id_companies_id_fk": { + "name": "tool_profiles_company_id_companies_id_fk", + "tableFrom": "tool_profiles", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_rate_limit_counters": { + "name": "tool_rate_limit_counters", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "policy_id": { + "name": "policy_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "counter_key": { + "name": "counter_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_kind": { + "name": "window_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_start_at": { + "name": "window_start_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "limit": { + "name": "limit", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "remaining": { + "name": "remaining", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "reset_at": { + "name": "reset_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": { + "tool_rate_limit_counters_company_idx": { + "name": "tool_rate_limit_counters_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_rate_limit_counters_window_uq": { + "name": "tool_rate_limit_counters_window_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "policy_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "counter_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_start_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_rate_limit_counters_company_id_companies_id_fk": { + "name": "tool_rate_limit_counters_company_id_companies_id_fk", + "tableFrom": "tool_rate_limit_counters", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_rate_limit_counters_policy_id_tool_policies_id_fk": { + "name": "tool_rate_limit_counters_policy_id_tool_policies_id_fk", + "tableFrom": "tool_rate_limit_counters", + "tableTo": "tool_policies", + "columnsFrom": [ + "policy_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_runtime_metric_counters": { + "name": "tool_runtime_metric_counters", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bucket_start_at": { + "name": "bucket_start_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_runtime_metric_counters_company_metric_idx": { + "name": "tool_runtime_metric_counters_company_metric_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bucket_start_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_runtime_metric_counters_bucket_uq": { + "name": "tool_runtime_metric_counters_bucket_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bucket_start_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_runtime_metric_counters_company_id_companies_id_fk": { + "name": "tool_runtime_metric_counters_company_id_companies_id_fk", + "tableFrom": "tool_runtime_metric_counters", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_runtime_slots": { + "name": "tool_runtime_slots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_scope_type": { + "name": "owner_scope_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'connection'" + }, + "owner_scope_id": { + "name": "owner_scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "runtime_kind": { + "name": "runtime_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_stdio'" + }, + "slot_key": { + "name": "slot_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'stopped'" + }, + "reuse_key": { + "name": "reuse_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_scope": { + "name": "workspace_scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_scope_hash": { + "name": "credential_scope_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_ref": { + "name": "provider_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "process_id": { + "name": "process_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "command_template_key": { + "name": "command_template_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unchecked'" + }, + "health_message": { + "name": "health_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_health_check_at": { + "name": "last_health_check_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_started_at": { + "name": "last_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "stopped_at": { + "name": "stopped_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "idle_expires_at": { + "name": "idle_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "idle_deadline_at": { + "name": "idle_deadline_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_runtime_slots_company_idx": { + "name": "tool_runtime_slots_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_runtime_slots_connection_idx": { + "name": "tool_runtime_slots_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_runtime_slots_execution_workspace_idx": { + "name": "tool_runtime_slots_execution_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_runtime_slots_slot_key_uq": { + "name": "tool_runtime_slots_slot_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slot_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_runtime_slots_company_id_companies_id_fk": { + "name": "tool_runtime_slots_company_id_companies_id_fk", + "tableFrom": "tool_runtime_slots", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_runtime_slots_application_id_tool_applications_id_fk": { + "name": "tool_runtime_slots_application_id_tool_applications_id_fk", + "tableFrom": "tool_runtime_slots", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_runtime_slots_connection_id_tool_connections_id_fk": { + "name": "tool_runtime_slots_connection_id_tool_connections_id_fk", + "tableFrom": "tool_runtime_slots", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_runtime_slots_project_workspace_id_project_workspaces_id_fk": { + "name": "tool_runtime_slots_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "tool_runtime_slots", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_runtime_slots_execution_workspace_id_execution_workspaces_id_fk": { + "name": "tool_runtime_slots_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "tool_runtime_slots", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_runtime_slots_issue_id_issues_id_fk": { + "name": "tool_runtime_slots_issue_id_issues_id_fk", + "tableFrom": "tool_runtime_slots", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_stdio_command_templates": { + "name": "tool_stdio_command_templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "template_key": { + "name": "template_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "args": { + "name": "args", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "env_keys": { + "name": "env_keys", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "tools": { + "name": "tools", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "disabled_at": { + "name": "disabled_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": { + "tool_stdio_command_templates_company_idx": { + "name": "tool_stdio_command_templates_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_stdio_command_templates_company_status_idx": { + "name": "tool_stdio_command_templates_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_stdio_command_templates_company_key_uq": { + "name": "tool_stdio_command_templates_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "template_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_stdio_command_templates_company_id_companies_id_fk": { + "name": "tool_stdio_command_templates_company_id_companies_id_fk", + "tableFrom": "tool_stdio_command_templates", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_stdio_command_templates_created_by_agent_id_agents_id_fk": { + "name": "tool_stdio_command_templates_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_stdio_command_templates", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_inbox_agent_policies": { + "name": "user_inbox_agent_policies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "allowed_agent_ids": { + "name": "allowed_agent_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()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_inbox_agent_policies_company_user_uq": { + "name": "user_inbox_agent_policies_company_user_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_inbox_agent_policies_allowed_agent_ids_idx": { + "name": "user_inbox_agent_policies_allowed_agent_ids_idx", + "columns": [ + { + "expression": "allowed_agent_ids", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "user_inbox_agent_policies_company_id_companies_id_fk": { + "name": "user_inbox_agent_policies_company_id_companies_id_fk", + "tableFrom": "user_inbox_agent_policies", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "user_inbox_agent_policies_mode_check": { + "name": "user_inbox_agent_policies_mode_check", + "value": "\"user_inbox_agent_policies\".\"mode\" in ('open', 'allowlist', 'disabled')" + } + }, + "isRLSEnabled": false + }, + "public.user_secret_declarations": { + "name": "user_secret_declarations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_secret_definition_id": { + "name": "user_secret_definition_id", + "type": "uuid", + "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 + }, + "config_path": { + "name": "config_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env_key": { + "name": "env_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version_selector": { + "name": "version_selector", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'latest'" + }, + "required": { + "name": "required", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "allow_missing_override": { + "name": "allow_missing_override", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "label": { + "name": "label", + "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": { + "user_secret_declarations_company_idx": { + "name": "user_secret_declarations_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_declarations_definition_idx": { + "name": "user_secret_declarations_definition_idx", + "columns": [ + { + "expression": "user_secret_definition_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_declarations_target_idx": { + "name": "user_secret_declarations_target_idx", + "columns": [ + { + "expression": "company_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": {} + }, + "user_secret_declarations_company_required_idx": { + "name": "user_secret_declarations_company_required_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "required", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_declarations_target_path_uq": { + "name": "user_secret_declarations_target_path_uq", + "columns": [ + { + "expression": "company_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" + }, + { + "expression": "config_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_declarations_required_override_idx": { + "name": "user_secret_declarations_required_override_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "allow_missing_override", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"user_secret_declarations\".\"allow_missing_override\" = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_secret_declarations_company_id_companies_id_fk": { + "name": "user_secret_declarations_company_id_companies_id_fk", + "tableFrom": "user_secret_declarations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_secret_declarations_user_secret_definition_id_user_secret_definitions_id_fk": { + "name": "user_secret_declarations_user_secret_definition_id_user_secret_definitions_id_fk", + "tableFrom": "user_secret_declarations", + "tableTo": "user_secret_definitions", + "columnsFrom": [ + "user_secret_definition_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_secret_definitions": { + "name": "user_secret_definitions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_encrypted'" + }, + "managed_mode": { + "name": "managed_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip_managed'" + }, + "provider_config_id": { + "name": "provider_config_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider_metadata": { + "name": "provider_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "usage_guidance": { + "name": "usage_guidance", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_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": { + "user_secret_definitions_company_status_idx": { + "name": "user_secret_definitions_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_definitions_company_provider_idx": { + "name": "user_secret_definitions_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_definitions_provider_config_idx": { + "name": "user_secret_definitions_provider_config_idx", + "columns": [ + { + "expression": "provider_config_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_definitions_company_key_uq": { + "name": "user_secret_definitions_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"user_secret_definitions\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_secret_definitions_company_id_companies_id_fk": { + "name": "user_secret_definitions_company_id_companies_id_fk", + "tableFrom": "user_secret_definitions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_secret_definitions_provider_config_id_company_secret_provider_configs_id_fk": { + "name": "user_secret_definitions_provider_config_id_company_secret_provider_configs_id_fk", + "tableFrom": "user_secret_definitions", + "tableTo": "company_secret_provider_configs", + "columnsFrom": [ + "provider_config_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "user_secret_definitions_created_by_agent_id_agents_id_fk": { + "name": "user_secret_definitions_created_by_agent_id_agents_id_fk", + "tableFrom": "user_secret_definitions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "user_secret_definitions_updated_by_agent_id_agents_id_fk": { + "name": "user_secret_definitions_updated_by_agent_id_agents_id_fk", + "tableFrom": "user_secret_definitions", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_sidebar_preferences": { + "name": "user_sidebar_preferences", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "company_order": { + "name": "company_order", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_sidebar_preferences_user_uq": { + "name": "user_sidebar_preferences_user_uq", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.work_assessments": { + "name": "work_assessments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "turn_id": { + "name": "turn_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "contract_id": { + "name": "contract_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "result_id": { + "name": "result_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trigger_kind": { + "name": "trigger_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trigger_ref": { + "name": "trigger_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trigger_capability": { + "name": "trigger_capability", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trigger_actor_company_id": { + "name": "trigger_actor_company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "prior_issue_status": { + "name": "prior_issue_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prior_status_version": { + "name": "prior_status_version", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "prior_decision_id": { + "name": "prior_decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "policy_version": { + "name": "policy_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "assessment_json": { + "name": "assessment_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "input_digest": { + "name": "input_digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "supersedes_assessment_id": { + "name": "supersedes_assessment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "work_assessments_company_issue_input_uq": { + "name": "work_assessments_company_issue_input_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "input_digest", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "work_assessments_company_id_companies_id_fk": { + "name": "work_assessments_company_id_companies_id_fk", + "tableFrom": "work_assessments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "work_assessments_trigger_actor_company_id_companies_id_fk": { + "name": "work_assessments_trigger_actor_company_id_companies_id_fk", + "tableFrom": "work_assessments", + "tableTo": "companies", + "columnsFrom": [ + "trigger_actor_company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "work_assessments_issue_company_fk": { + "name": "work_assessments_issue_company_fk", + "tableFrom": "work_assessments", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "work_assessments_run_owner_fk": { + "name": "work_assessments_run_owner_fk", + "tableFrom": "work_assessments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id" + ], + "columnsTo": [ + "company_id", + "native_issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "work_assessments_contract_owner_fk": { + "name": "work_assessments_contract_owner_fk", + "tableFrom": "work_assessments", + "tableTo": "completion_contracts", + "columnsFrom": [ + "company_id", + "issue_id", + "contract_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "work_assessments_result_owner_fk": { + "name": "work_assessments_result_owner_fk", + "tableFrom": "work_assessments", + "tableTo": "native_run_results", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "result_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "run_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "work_assessments_supersedes_owner_fk": { + "name": "work_assessments_supersedes_owner_fk", + "tableFrom": "work_assessments", + "tableTo": "work_assessments", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "supersedes_assessment_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "run_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "work_assessments_company_issue_run_id_uq": { + "name": "work_assessments_company_issue_run_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "issue_id", + "run_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "work_assessments_trigger_actor_company_check": { + "name": "work_assessments_trigger_actor_company_check", + "value": "\"work_assessments\".\"trigger_actor_company_id\" = \"work_assessments\".\"company_id\"" + } + }, + "isRLSEnabled": false + }, + "public.workspace_operations": { + "name": "workspace_operations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "exit_code": { + "name": "exit_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "log_store": { + "name": "log_store", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_ref": { + "name": "log_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_bytes": { + "name": "log_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "log_sha256": { + "name": "log_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_compressed": { + "name": "log_compressed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "stdout_excerpt": { + "name": "stdout_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stderr_excerpt": { + "name": "stderr_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_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": { + "workspace_operations_company_run_started_idx": { + "name": "workspace_operations_company_run_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_operations_company_workspace_started_idx": { + "name": "workspace_operations_company_workspace_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_operations_company_workspace_issue_started_idx": { + "name": "workspace_operations_company_workspace_issue_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_operations_company_id_companies_id_fk": { + "name": "workspace_operations_company_id_companies_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_operations_execution_workspace_id_execution_workspaces_id_fk": { + "name": "workspace_operations_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_operations_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "workspace_operations_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_operations_issue_id_issues_id_fk": { + "name": "workspace_operations_issue_id_issues_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_runtime_services": { + "name": "workspace_runtime_services", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "service_name": { + "name": "service_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lifecycle": { + "name": "lifecycle", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reuse_key": { + "name": "reuse_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_ref": { + "name": "provider_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "started_by_run_id": { + "name": "started_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "stopped_at": { + "name": "stopped_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "stop_policy": { + "name": "stop_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "exposure": { + "name": "exposure", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "exposure_handle": { + "name": "exposure_handle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "backend_url": { + "name": "backend_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "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": { + "workspace_runtime_services_company_workspace_status_idx": { + "name": "workspace_runtime_services_company_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_execution_workspace_status_idx": { + "name": "workspace_runtime_services_company_execution_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_project_status_idx": { + "name": "workspace_runtime_services_company_project_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_run_idx": { + "name": "workspace_runtime_services_run_idx", + "columns": [ + { + "expression": "started_by_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_updated_idx": { + "name": "workspace_runtime_services_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_runtime_services_company_id_companies_id_fk": { + "name": "workspace_runtime_services_company_id_companies_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workspace_runtime_services_project_id_projects_id_fk": { + "name": "workspace_runtime_services_project_id_projects_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_project_workspace_id_project_workspaces_id_fk": { + "name": "workspace_runtime_services_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_execution_workspace_id_execution_workspaces_id_fk": { + "name": "workspace_runtime_services_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_issue_id_issues_id_fk": { + "name": "workspace_runtime_services_issue_id_issues_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_owner_agent_id_agents_id_fk": { + "name": "workspace_runtime_services_owner_agent_id_agents_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_started_by_run_id_heartbeat_runs_id_fk": { + "name": "workspace_runtime_services_started_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "started_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": { + "public.chat_telegram_draft_ids": { + "name": "chat_telegram_draft_ids", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/db/src/migrations/meta/0277_snapshot.json b/packages/db/src/migrations/meta/0277_snapshot.json new file mode 100644 index 0000000000..a03c784566 --- /dev/null +++ b/packages/db/src/migrations/meta/0277_snapshot.json @@ -0,0 +1,48279 @@ +{ + "id": "7350441d-5d86-425c-9186-8aaa1e3a796b", + "prevId": "9c7c9e05-e663-4a29-8b46-63733f62da9b", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.activity_log": { + "name": "activity_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "activity_log_company_created_idx": { + "name": "activity_log_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "activity_log_company_agent_created_idx": { + "name": "activity_log_company_agent_created_idx", + "columns": [ + { + "expression": "company_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": {} + }, + "activity_log_company_responsible_user_created_idx": { + "name": "activity_log_company_responsible_user_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "responsible_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "activity_log_run_id_idx": { + "name": "activity_log_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "activity_log_entity_type_id_idx": { + "name": "activity_log_entity_type_id_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "activity_log_company_id_companies_id_fk": { + "name": "activity_log_company_id_companies_id_fk", + "tableFrom": "activity_log", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "activity_log_agent_id_agents_id_fk": { + "name": "activity_log_agent_id_agents_id_fk", + "tableFrom": "activity_log", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "activity_log_run_id_heartbeat_runs_id_fk": { + "name": "activity_log_run_id_heartbeat_runs_id_fk", + "tableFrom": "activity_log", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.adapter_auth_sessions": { + "name": "adapter_auth_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "environment_id": { + "name": "environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "ai_connection": { + "name": "ai_connection", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_grant_id": { + "name": "connection_grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_method": { + "name": "connection_method", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_by_user_id": { + "name": "started_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public_session_id": { + "name": "public_session_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "provider_lease_id": { + "name": "provider_lease_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'starting'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "promotion_expires_at": { + "name": "promotion_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "bound_at": { + "name": "bound_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_claim": { + "name": "result_claim", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "adapter_auth_sessions_company_status_idx": { + "name": "adapter_auth_sessions_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "adapter_auth_sessions_company_owner_adapter_active_uq": { + "name": "adapter_auth_sessions_company_owner_adapter_active_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "adapter_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"adapter_auth_sessions\".\"status\" IN ('starting', 'waiting_for_user', 'promoting', 'awaiting_code', 'submitting')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "adapter_auth_sessions_public_session_id_uq": { + "name": "adapter_auth_sessions_public_session_id_uq", + "columns": [ + { + "expression": "public_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "adapter_auth_sessions_environment_idx": { + "name": "adapter_auth_sessions_environment_idx", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "adapter_auth_sessions_expires_idx": { + "name": "adapter_auth_sessions_expires_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "adapter_auth_sessions_provider_lease_idx": { + "name": "adapter_auth_sessions_provider_lease_idx", + "columns": [ + { + "expression": "provider_lease_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "adapter_auth_sessions_company_id_companies_id_fk": { + "name": "adapter_auth_sessions_company_id_companies_id_fk", + "tableFrom": "adapter_auth_sessions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "adapter_auth_sessions_environment_id_environments_id_fk": { + "name": "adapter_auth_sessions_environment_id_environments_id_fk", + "tableFrom": "adapter_auth_sessions", + "tableTo": "environments", + "columnsFrom": [ + "environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_api_keys": { + "name": "agent_api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope_config": { + "name": "scope_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "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 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_api_keys_key_hash_idx": { + "name": "agent_api_keys_key_hash_idx", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_api_keys_company_agent_idx": { + "name": "agent_api_keys_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_api_keys_agent_id_agents_id_fk": { + "name": "agent_api_keys_agent_id_agents_id_fk", + "tableFrom": "agent_api_keys", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_api_keys_company_id_companies_id_fk": { + "name": "agent_api_keys_company_id_companies_id_fk", + "tableFrom": "agent_api_keys", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_config_revisions": { + "name": "agent_config_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'patch'" + }, + "rolled_back_from_revision_id": { + "name": "rolled_back_from_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "changed_keys": { + "name": "changed_keys", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "before_config": { + "name": "before_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "after_config": { + "name": "after_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_config_revisions_company_agent_created_idx": { + "name": "agent_config_revisions_company_agent_created_idx", + "columns": [ + { + "expression": "company_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": {} + }, + "agent_config_revisions_agent_created_idx": { + "name": "agent_config_revisions_agent_created_idx", + "columns": [ + { + "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_config_revisions_company_id_companies_id_fk": { + "name": "agent_config_revisions_company_id_companies_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_config_revisions_agent_id_agents_id_fk": { + "name": "agent_config_revisions_agent_id_agents_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_config_revisions_created_by_agent_id_agents_id_fk": { + "name": "agent_config_revisions_created_by_agent_id_agents_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_memberships": { + "name": "agent_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'joined'" + }, + "starred_at": { + "name": "starred_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": { + "agent_memberships_company_user_idx": { + "name": "agent_memberships_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_memberships_company_user_starred_idx": { + "name": "agent_memberships_company_user_starred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "starred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_memberships_agent_idx": { + "name": "agent_memberships_agent_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_memberships_company_user_agent_uq": { + "name": "agent_memberships_company_user_agent_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_memberships_company_id_companies_id_fk": { + "name": "agent_memberships_company_id_companies_id_fk", + "tableFrom": "agent_memberships", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_memberships_agent_id_agents_id_fk": { + "name": "agent_memberships_agent_id_agents_id_fk", + "tableFrom": "agent_memberships", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_runtime_state": { + "name": "agent_runtime_state", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state_json": { + "name": "state_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_run_id": { + "name": "last_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_run_status": { + "name": "last_run_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "total_input_tokens": { + "name": "total_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_output_tokens": { + "name": "total_output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cached_input_tokens": { + "name": "total_cached_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cost_cents": { + "name": "total_cost_cents", + "type": "bigint", + "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": { + "agent_runtime_state_company_agent_idx": { + "name": "agent_runtime_state_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_runtime_state_company_updated_idx": { + "name": "agent_runtime_state_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_runtime_state_agent_id_agents_id_fk": { + "name": "agent_runtime_state_agent_id_agents_id_fk", + "tableFrom": "agent_runtime_state", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_runtime_state_company_id_companies_id_fk": { + "name": "agent_runtime_state_company_id_companies_id_fk", + "tableFrom": "agent_runtime_state", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_session_goal_actions": { + "name": "agent_session_goal_actions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload_json": { + "name": "payload_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "result_json": { + "name": "result_json", + "type": "jsonb", + "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()" + }, + "delivered_at": { + "name": "delivered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_session_goal_actions_session_request_uniq": { + "name": "agent_session_goal_actions_session_request_uniq", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_session_goal_actions_company_status_created_idx": { + "name": "agent_session_goal_actions_company_status_created_idx", + "columns": [ + { + "expression": "company_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": {} + }, + "agent_session_goal_actions_session_created_idx": { + "name": "agent_session_goal_actions_session_created_idx", + "columns": [ + { + "expression": "session_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_session_goal_actions_company_id_companies_id_fk": { + "name": "agent_session_goal_actions_company_id_companies_id_fk", + "tableFrom": "agent_session_goal_actions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_session_goal_actions_session_id_agent_task_sessions_id_fk": { + "name": "agent_session_goal_actions_session_id_agent_task_sessions_id_fk", + "tableFrom": "agent_session_goal_actions", + "tableTo": "agent_task_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_task_sessions": { + "name": "agent_task_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "task_key": { + "name": "task_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_params_json": { + "name": "session_params_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "session_display_id": { + "name": "session_display_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_run_id": { + "name": "last_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "goal_capability_json": { + "name": "goal_capability_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "goal_json": { + "name": "goal_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "goal_status": { + "name": "goal_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "goal_desired_state": { + "name": "goal_desired_state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "goal_source_id": { + "name": "goal_source_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "goal_source_cursor": { + "name": "goal_source_cursor", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "goal_revision": { + "name": "goal_revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "goal_observed_at": { + "name": "goal_observed_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": { + "agent_task_sessions_company_agent_adapter_task_uniq": { + "name": "agent_task_sessions_company_agent_adapter_task_uniq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "adapter_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "task_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_task_sessions_company_agent_updated_idx": { + "name": "agent_task_sessions_company_agent_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_task_sessions_company_task_updated_idx": { + "name": "agent_task_sessions_company_task_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "task_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_task_sessions_company_id_companies_id_fk": { + "name": "agent_task_sessions_company_id_companies_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_task_sessions_agent_id_agents_id_fk": { + "name": "agent_task_sessions_agent_id_agents_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_task_sessions_last_run_id_heartbeat_runs_id_fk": { + "name": "agent_task_sessions_last_run_id_heartbeat_runs_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "last_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_wakeup_requests": { + "name": "agent_wakeup_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trigger_detail": { + "name": "trigger_detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "coalesced_count": { + "name": "coalesced_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "requested_by_actor_type": { + "name": "requested_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_by_actor_id": { + "name": "requested_by_actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_at": { + "name": "requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_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": { + "agent_wakeup_requests_company_agent_status_idx": { + "name": "agent_wakeup_requests_company_agent_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_company_requested_idx": { + "name": "agent_wakeup_requests_company_requested_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_agent_requested_idx": { + "name": "agent_wakeup_requests_agent_requested_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_review_path_recovery_idempotency_uq": { + "name": "agent_wakeup_requests_review_path_recovery_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_wakeup_requests\".\"idempotency_key\" LIKE 'issue_review_path_lost:%' AND \"agent_wakeup_requests\".\"status\" <> 'skipped'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_disposition_repair_idempotency_uq": { + "name": "agent_wakeup_requests_disposition_repair_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_wakeup_requests\".\"idempotency_key\" LIKE 'issue_disposition_repair:%' AND \"agent_wakeup_requests\".\"status\" <> 'skipped'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_question_response_delivery_idempotency_uq": { + "name": "agent_wakeup_requests_question_response_delivery_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "(\"agent_wakeup_requests\".\"idempotency_key\" LIKE 'question-response:%' OR \"agent_wakeup_requests\".\"idempotency_key\" LIKE 'interaction:%') AND \"agent_wakeup_requests\".\"status\" NOT IN ('skipped', 'failed', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_connection_intent_delivery_idempotency_uq": { + "name": "agent_wakeup_requests_connection_intent_delivery_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_wakeup_requests\".\"idempotency_key\" LIKE 'connection-intent:%' AND \"agent_wakeup_requests\".\"status\" NOT IN ('skipped', 'failed', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_tool_action_delivery_uq": { + "name": "agent_wakeup_requests_tool_action_delivery_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_wakeup_requests\".\"idempotency_key\" LIKE 'tool-action-response:%' AND \"agent_wakeup_requests\".\"status\" NOT IN ('skipped', 'failed', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_company_payload_issue_idx": { + "name": "agent_wakeup_requests_company_payload_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "(\"payload\" ->> 'issueId')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_wakeup_requests_company_id_companies_id_fk": { + "name": "agent_wakeup_requests_company_id_companies_id_fk", + "tableFrom": "agent_wakeup_requests", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_wakeup_requests_agent_id_agents_id_fk": { + "name": "agent_wakeup_requests_agent_id_agents_id_fk", + "tableFrom": "agent_wakeup_requests", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agents": { + "name": "agents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'general'" + }, + "title": { + "name": "title", + "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": "'idle'" + }, + "reports_to": { + "name": "reports_to", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'process'" + }, + "adapter_config": { + "name": "adapter_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "runtime_config": { + "name": "runtime_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "default_environment_id": { + "name": "default_environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "budget_monthly_cents": { + "name": "budget_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "spent_monthly_cents": { + "name": "spent_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_reason": { + "name": "error_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_heartbeat_at": { + "name": "last_heartbeat_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agents_company_status_idx": { + "name": "agents_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_company_reports_to_idx": { + "name": "agents_company_reports_to_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "reports_to", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_company_default_environment_idx": { + "name": "agents_company_default_environment_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "default_environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agents_company_id_companies_id_fk": { + "name": "agents_company_id_companies_id_fk", + "tableFrom": "agents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agents_reports_to_agents_id_fk": { + "name": "agents_reports_to_agents_id_fk", + "tableFrom": "agents", + "tableTo": "agents", + "columnsFrom": [ + "reports_to" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agents_default_environment_id_environments_id_fk": { + "name": "agents_default_environment_id_environments_id_fk", + "tableFrom": "agents", + "tableTo": "environments", + "columnsFrom": [ + "default_environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "agents_company_id_uq": { + "name": "agents_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_connection_defaults": { + "name": "ai_connection_defaults", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "method": { + "name": "method", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ai_connection_defaults_owner_method_uq": { + "name": "ai_connection_defaults_owner_method_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "method", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_connection_defaults_company_id_companies_id_fk": { + "name": "ai_connection_defaults_company_id_companies_id_fk", + "tableFrom": "ai_connection_defaults", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_connection_defaults_company_grant_fk": { + "name": "ai_connection_defaults_company_grant_fk", + "tableFrom": "ai_connection_defaults", + "tableTo": "connection_grants", + "columnsFrom": [ + "company_id", + "grant_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "ai_connection_defaults_provider_check": { + "name": "ai_connection_defaults_provider_check", + "value": "\"ai_connection_defaults\".\"provider\" in ('anthropic','openai','openrouter','xai')" + }, + "ai_connection_defaults_method_check": { + "name": "ai_connection_defaults_method_check", + "value": "\"ai_connection_defaults\".\"method\" in ('subscription','api_key')" + } + }, + "isRLSEnabled": false + }, + "public.approval_comments": { + "name": "approval_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "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": { + "approval_comments_company_idx": { + "name": "approval_comments_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "approval_comments_approval_idx": { + "name": "approval_comments_approval_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "approval_comments_approval_created_idx": { + "name": "approval_comments_approval_created_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "approval_comments_company_id_companies_id_fk": { + "name": "approval_comments_company_id_companies_id_fk", + "tableFrom": "approval_comments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approval_comments_approval_id_approvals_id_fk": { + "name": "approval_comments_approval_id_approvals_id_fk", + "tableFrom": "approval_comments", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approval_comments_author_agent_id_agents_id_fk": { + "name": "approval_comments_author_agent_id_agents_id_fk", + "tableFrom": "approval_comments", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_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, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requested_by_agent_id": { + "name": "requested_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_by_user_id": { + "name": "requested_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "decision_note": { + "name": "decision_note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_by_user_id": { + "name": "decided_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_at": { + "name": "decided_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": { + "approvals_company_status_type_idx": { + "name": "approvals_company_status_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "approvals_company_id_companies_id_fk": { + "name": "approvals_company_id_companies_id_fk", + "tableFrom": "approvals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approvals_requested_by_agent_id_agents_id_fk": { + "name": "approvals_requested_by_agent_id_agents_id_fk", + "tableFrom": "approvals", + "tableTo": "agents", + "columnsFrom": [ + "requested_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.assets": { + "name": "assets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "object_key": { + "name": "object_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "byte_size": { + "name": "byte_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "sha256": { + "name": "sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "original_filename": { + "name": "original_filename", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "assets_company_created_idx": { + "name": "assets_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "assets_company_provider_idx": { + "name": "assets_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "assets_company_object_key_uq": { + "name": "assets_company_object_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "assets_company_id_companies_id_fk": { + "name": "assets_company_id_companies_id_fk", + "tableFrom": "assets", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "assets_created_by_agent_id_agents_id_fk": { + "name": "assets_created_by_agent_id_agents_id_fk", + "tableFrom": "assets", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "issuer": { + "name": "issuer", + "type": "text", + "primaryKey": false, + "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 + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "account_issuer_account_id_uq": { + "name": "account_issuer_account_id_uq", + "columns": [ + { + "expression": "issuer", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_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 + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.board_api_keys": { + "name": "board_api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "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 + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "board_api_keys_key_hash_idx": { + "name": "board_api_keys_key_hash_idx", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "board_api_keys_user_idx": { + "name": "board_api_keys_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "board_api_keys_user_id_user_id_fk": { + "name": "board_api_keys_user_id_user_id_fk", + "tableFrom": "board_api_keys", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.budget_incidents": { + "name": "budget_incidents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "policy_id": { + "name": "policy_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_kind": { + "name": "window_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_start": { + "name": "window_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "window_end": { + "name": "window_end", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "threshold_type": { + "name": "threshold_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount_limit": { + "name": "amount_limit", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "amount_observed": { + "name": "amount_observed", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "budget_incidents_company_status_idx": { + "name": "budget_incidents_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_incidents_company_scope_idx": { + "name": "budget_incidents_company_scope_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_incidents_policy_window_threshold_idx": { + "name": "budget_incidents_policy_window_threshold_idx", + "columns": [ + { + "expression": "policy_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_start", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "threshold_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"budget_incidents\".\"status\" <> 'dismissed'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "budget_incidents_company_id_companies_id_fk": { + "name": "budget_incidents_company_id_companies_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "budget_incidents_policy_id_budget_policies_id_fk": { + "name": "budget_incidents_policy_id_budget_policies_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "budget_policies", + "columnsFrom": [ + "policy_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "budget_incidents_approval_id_approvals_id_fk": { + "name": "budget_incidents_approval_id_approvals_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.budget_policies": { + "name": "budget_policies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'billed_cents'" + }, + "window_kind": { + "name": "window_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "warn_percent": { + "name": "warn_percent", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 80 + }, + "hard_stop_enabled": { + "name": "hard_stop_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "notify_enabled": { + "name": "notify_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "budget_policies_company_scope_active_idx": { + "name": "budget_policies_company_scope_active_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_policies_company_window_idx": { + "name": "budget_policies_company_window_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_policies_company_scope_metric_unique_idx": { + "name": "budget_policies_company_scope_metric_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "budget_policies_company_id_companies_id_fk": { + "name": "budget_policies_company_id_companies_id_fk", + "tableFrom": "budget_policies", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.built_in_managed_resources": { + "name": "built_in_managed_resources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "bundle_key": { + "name": "bundle_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_kind": { + "name": "resource_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_key": { + "name": "resource_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "stock_version": { + "name": "stock_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stock_hash": { + "name": "stock_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "defaults_json": { + "name": "defaults_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "built_in_managed_resources_company_idx": { + "name": "built_in_managed_resources_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "built_in_managed_resources_resource_idx": { + "name": "built_in_managed_resources_resource_idx", + "columns": [ + { + "expression": "resource_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "built_in_managed_resources_company_bundle_resource_uq": { + "name": "built_in_managed_resources_company_bundle_resource_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bundle_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "built_in_managed_resources_company_id_companies_id_fk": { + "name": "built_in_managed_resources_company_id_companies_id_fk", + "tableFrom": "built_in_managed_resources", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.case_attachments": { + "name": "case_attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_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": { + "case_attachments_company_case_idx": { + "name": "case_attachments_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_attachments_asset_uq": { + "name": "case_attachments_asset_uq", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "case_attachments_company_id_companies_id_fk": { + "name": "case_attachments_company_id_companies_id_fk", + "tableFrom": "case_attachments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_attachments_case_id_cases_id_fk": { + "name": "case_attachments_case_id_cases_id_fk", + "tableFrom": "case_attachments", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_attachments_asset_id_assets_id_fk": { + "name": "case_attachments_asset_id_assets_id_fk", + "tableFrom": "case_attachments", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.case_documents": { + "name": "case_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "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": { + "case_documents_company_case_key_uq": { + "name": "case_documents_company_case_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_documents_document_uq": { + "name": "case_documents_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_documents_company_case_updated_idx": { + "name": "case_documents_company_case_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "case_documents_company_id_companies_id_fk": { + "name": "case_documents_company_id_companies_id_fk", + "tableFrom": "case_documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_documents_case_id_cases_id_fk": { + "name": "case_documents_case_id_cases_id_fk", + "tableFrom": "case_documents", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_documents_document_id_documents_id_fk": { + "name": "case_documents_document_id_documents_id_fk", + "tableFrom": "case_documents", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.case_events": { + "name": "case_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_agent_id": { + "name": "actor_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "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()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "case_events_case_created_idx": { + "name": "case_events_case_created_idx", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_events_company_case_idx": { + "name": "case_events_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "case_events_company_id_companies_id_fk": { + "name": "case_events_company_id_companies_id_fk", + "tableFrom": "case_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_events_case_id_cases_id_fk": { + "name": "case_events_case_id_cases_id_fk", + "tableFrom": "case_events", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_events_actor_agent_id_agents_id_fk": { + "name": "case_events_actor_agent_id_agents_id_fk", + "tableFrom": "case_events", + "tableTo": "agents", + "columnsFrom": [ + "actor_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "case_events_kind_check": { + "name": "case_events_kind_check", + "value": "\"case_events\".\"kind\" in (\n 'created',\n 'updated',\n 'fields_changed',\n 'status_changed',\n 'issue_linked',\n 'issue_unlinked',\n 'document_revised',\n 'child_linked',\n 'attachment_added',\n 'label_added',\n 'label_removed'\n )" + }, + "case_events_actor_type_check": { + "name": "case_events_actor_type_check", + "value": "\"case_events\".\"actor_type\" in ('user', 'agent', 'system')" + } + }, + "isRLSEnabled": false + }, + "public.case_issue_links": { + "name": "case_issue_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_run_id": { + "name": "created_by_run_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": { + "case_issue_links_case_issue_uq": { + "name": "case_issue_links_case_issue_uq", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_issue_links_company_case_idx": { + "name": "case_issue_links_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_issue_links_issue_idx": { + "name": "case_issue_links_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "case_issue_links_company_id_companies_id_fk": { + "name": "case_issue_links_company_id_companies_id_fk", + "tableFrom": "case_issue_links", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_issue_links_case_id_cases_id_fk": { + "name": "case_issue_links_case_id_cases_id_fk", + "tableFrom": "case_issue_links", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_issue_links_issue_id_issues_id_fk": { + "name": "case_issue_links_issue_id_issues_id_fk", + "tableFrom": "case_issue_links", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "case_issue_links_role_check": { + "name": "case_issue_links_role_check", + "value": "\"case_issue_links\".\"role\" in ('origin', 'work', 'reference')" + } + }, + "isRLSEnabled": false + }, + "public.case_labels": { + "name": "case_labels", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label_id": { + "name": "label_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": { + "case_labels_case_label_uq": { + "name": "case_labels_case_label_uq", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "label_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_labels_company_case_idx": { + "name": "case_labels_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_labels_label_idx": { + "name": "case_labels_label_idx", + "columns": [ + { + "expression": "label_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "case_labels_company_id_companies_id_fk": { + "name": "case_labels_company_id_companies_id_fk", + "tableFrom": "case_labels", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_labels_case_id_cases_id_fk": { + "name": "case_labels_case_id_cases_id_fk", + "tableFrom": "case_labels", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_labels_label_id_labels_id_fk": { + "name": "case_labels_label_id_labels_id_fk", + "tableFrom": "case_labels", + "tableTo": "labels", + "columnsFrom": [ + "label_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cases": { + "name": "cases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "case_number": { + "name": "case_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "case_type": { + "name": "case_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "fields": { + "name": "fields", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "parent_case_id": { + "name": "parent_case_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "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": { + "cases_company_case_number_uq": { + "name": "cases_company_case_number_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_identifier_uq": { + "name": "cases_identifier_uq", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_company_type_key_uq": { + "name": "cases_company_type_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_company_status_idx": { + "name": "cases_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_company_type_idx": { + "name": "cases_company_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_company_project_idx": { + "name": "cases_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_parent_idx": { + "name": "cases_parent_idx", + "columns": [ + { + "expression": "parent_case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_title_search_idx": { + "name": "cases_title_search_idx", + "columns": [ + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "cases_identifier_search_idx": { + "name": "cases_identifier_search_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "cases_summary_search_idx": { + "name": "cases_summary_search_idx", + "columns": [ + { + "expression": "summary", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "cases_company_id_companies_id_fk": { + "name": "cases_company_id_companies_id_fk", + "tableFrom": "cases", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cases_project_id_projects_id_fk": { + "name": "cases_project_id_projects_id_fk", + "tableFrom": "cases", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cases_parent_case_id_cases_id_fk": { + "name": "cases_parent_case_id_cases_id_fk", + "tableFrom": "cases", + "tableTo": "cases", + "columnsFrom": [ + "parent_case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cases_created_by_agent_id_agents_id_fk": { + "name": "cases_created_by_agent_id_agents_id_fk", + "tableFrom": "cases", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "cases_status_check": { + "name": "cases_status_check", + "value": "\"cases\".\"status\" in ('draft', 'in_progress', 'in_review', 'approved', 'done', 'cancelled')" + } + }, + "isRLSEnabled": false + }, + "public.chat_actions": { + "name": "chat_actions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "delivery_id": { + "name": "delivery_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "principal_id": { + "name": "principal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_action_id": { + "name": "provider_action_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_actions_provider_action_uq": { + "name": "chat_actions_provider_action_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_action_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_actions_company_id_companies_id_fk": { + "name": "chat_actions_company_id_companies_id_fk", + "tableFrom": "chat_actions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_actions_delivery_id_chat_deliveries_id_fk": { + "name": "chat_actions_delivery_id_chat_deliveries_id_fk", + "tableFrom": "chat_actions", + "tableTo": "chat_deliveries", + "columnsFrom": [ + "delivery_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_actions_company_delivery_fk": { + "name": "chat_actions_company_delivery_fk", + "tableFrom": "chat_actions", + "tableTo": "chat_deliveries", + "columnsFrom": [ + "company_id", + "delivery_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_actions_company_conversation_fk": { + "name": "chat_actions_company_conversation_fk", + "tableFrom": "chat_actions", + "tableTo": "chat_conversations", + "columnsFrom": [ + "company_id", + "conversation_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_actions_company_principal_fk": { + "name": "chat_actions_company_principal_fk", + "tableFrom": "chat_actions", + "tableTo": "chat_external_principals", + "columnsFrom": [ + "company_id", + "principal_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_actions_company_endpoint_fk": { + "name": "chat_actions_company_endpoint_fk", + "tableFrom": "chat_actions", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_agent_routes": { + "name": "chat_agent_routes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_endpoint_id": { + "name": "source_endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "destination_endpoint_id": { + "name": "destination_endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "trigger_mode": { + "name": "trigger_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'explicit_mention'" + }, + "max_hops": { + "name": "max_hops", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_agent_routes_pair_uq": { + "name": "chat_agent_routes_pair_uq", + "columns": [ + { + "expression": "source_endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "destination_endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_agent_routes_company_id_companies_id_fk": { + "name": "chat_agent_routes_company_id_companies_id_fk", + "tableFrom": "chat_agent_routes", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_agent_routes_company_source_fk": { + "name": "chat_agent_routes_company_source_fk", + "tableFrom": "chat_agent_routes", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "source_endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_agent_routes_company_destination_fk": { + "name": "chat_agent_routes_company_destination_fk", + "tableFrom": "chat_agent_routes", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "destination_endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_agent_routes_hops_check": { + "name": "chat_agent_routes_hops_check", + "value": "\"chat_agent_routes\".\"max_hops\" between 1 and 8" + } + }, + "isRLSEnabled": false + }, + "public.chat_conversations": { + "name": "chat_conversations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "external_conversation_id": { + "name": "external_conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_thread_id": { + "name": "external_thread_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "session_generation": { + "name": "session_generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "external_label": { + "name": "external_label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_url": { + "name": "provider_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_direct_message": { + "name": "is_direct_message", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_activity_at": { + "name": "last_activity_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": { + "chat_conversations_issue_idx": { + "name": "chat_conversations_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_conversations_thread_uq": { + "name": "chat_conversations_thread_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_thread_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "session_generation", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_conversations_company_id_companies_id_fk": { + "name": "chat_conversations_company_id_companies_id_fk", + "tableFrom": "chat_conversations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_conversations_resource_id_chat_endpoint_resources_id_fk": { + "name": "chat_conversations_resource_id_chat_endpoint_resources_id_fk", + "tableFrom": "chat_conversations", + "tableTo": "chat_endpoint_resources", + "columnsFrom": [ + "resource_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_conversations_issue_id_issues_id_fk": { + "name": "chat_conversations_issue_id_issues_id_fk", + "tableFrom": "chat_conversations", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "chat_conversations_company_issue_fk": { + "name": "chat_conversations_company_issue_fk", + "tableFrom": "chat_conversations", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_conversations_company_endpoint_fk": { + "name": "chat_conversations_company_endpoint_fk", + "tableFrom": "chat_conversations", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_conversations_company_resource_fk": { + "name": "chat_conversations_company_resource_fk", + "tableFrom": "chat_conversations", + "tableTo": "chat_endpoint_resources", + "columnsFrom": [ + "company_id", + "resource_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_conversations_company_id_uq": { + "name": "chat_conversations_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "chat_conversations_state_check": { + "name": "chat_conversations_state_check", + "value": "\"chat_conversations\".\"state\" in ('active', 'waiting', 'completed', 'unavailable', 'endpoint_removed')" + } + }, + "isRLSEnabled": false + }, + "public.chat_deliveries": { + "name": "chat_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "principal_id": { + "name": "principal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider_event_id": { + "name": "provider_event_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deduplication_key": { + "name": "deduplication_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_kind": { + "name": "event_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "normalized_event": { + "name": "normalized_event", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "redacted_error": { + "name": "redacted_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "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 + }, + "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": { + "chat_deliveries_work_idx": { + "name": "chat_deliveries_work_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_deliveries_event_uq": { + "name": "chat_deliveries_event_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_deliveries_dedupe_uq": { + "name": "chat_deliveries_dedupe_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deduplication_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_deliveries_company_id_companies_id_fk": { + "name": "chat_deliveries_company_id_companies_id_fk", + "tableFrom": "chat_deliveries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_deliveries_conversation_id_chat_conversations_id_fk": { + "name": "chat_deliveries_conversation_id_chat_conversations_id_fk", + "tableFrom": "chat_deliveries", + "tableTo": "chat_conversations", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_deliveries_principal_id_chat_external_principals_id_fk": { + "name": "chat_deliveries_principal_id_chat_external_principals_id_fk", + "tableFrom": "chat_deliveries", + "tableTo": "chat_external_principals", + "columnsFrom": [ + "principal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_deliveries_company_endpoint_fk": { + "name": "chat_deliveries_company_endpoint_fk", + "tableFrom": "chat_deliveries", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_deliveries_company_conversation_fk": { + "name": "chat_deliveries_company_conversation_fk", + "tableFrom": "chat_deliveries", + "tableTo": "chat_conversations", + "columnsFrom": [ + "company_id", + "conversation_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_deliveries_company_principal_fk": { + "name": "chat_deliveries_company_principal_fk", + "tableFrom": "chat_deliveries", + "tableTo": "chat_external_principals", + "columnsFrom": [ + "company_id", + "principal_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_deliveries_company_id_uq": { + "name": "chat_deliveries_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "chat_deliveries_state_check": { + "name": "chat_deliveries_state_check", + "value": "\"chat_deliveries\".\"state\" in ('received', 'filtered', 'processing', 'processed', 'retry', 'failed')" + } + }, + "isRLSEnabled": false + }, + "public.chat_endpoint_leases": { + "name": "chat_endpoint_leases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "lease_key": { + "name": "lease_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "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": { + "chat_endpoint_leases_active_uq": { + "name": "chat_endpoint_leases_active_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lease_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoint_leases_expiry_idx": { + "name": "chat_endpoint_leases_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_endpoint_leases_company_id_companies_id_fk": { + "name": "chat_endpoint_leases_company_id_companies_id_fk", + "tableFrom": "chat_endpoint_leases", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_endpoint_leases_company_endpoint_fk": { + "name": "chat_endpoint_leases_company_endpoint_fk", + "tableFrom": "chat_endpoint_leases", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_endpoint_resources": { + "name": "chat_endpoint_resources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_resource_id": { + "name": "provider_resource_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_provider_resource_id": { + "name": "parent_provider_resource_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "detail": { + "name": "detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_url": { + "name": "provider_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "availability": { + "name": "availability", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'available'" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "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()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_endpoint_resources_endpoint_idx": { + "name": "chat_endpoint_resources_endpoint_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoint_resources_external_uq": { + "name": "chat_endpoint_resources_external_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_endpoint_resources_company_id_companies_id_fk": { + "name": "chat_endpoint_resources_company_id_companies_id_fk", + "tableFrom": "chat_endpoint_resources", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_endpoint_resources_company_endpoint_fk": { + "name": "chat_endpoint_resources_company_endpoint_fk", + "tableFrom": "chat_endpoint_resources", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_endpoint_resources_company_id_uq": { + "name": "chat_endpoint_resources_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "chat_endpoint_resources_availability_check": { + "name": "chat_endpoint_resources_availability_check", + "value": "\"chat_endpoint_resources\".\"availability\" in ('available', 'unavailable', 'removed')" + } + }, + "isRLSEnabled": false + }, + "public.chat_endpoints": { + "name": "chat_endpoints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public_id": { + "name": "public_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "publication_mode": { + "name": "publication_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'automatic'" + }, + "external_execution_policy": { + "name": "external_execution_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'restricted'" + }, + "assigned_agent_id": { + "name": "assigned_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sponsor_user_id": { + "name": "sponsor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "deployment_mode": { + "name": "deployment_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'direct'" + }, + "provider_account_id": { + "name": "provider_account_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_account_label": { + "name": "provider_account_label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bot_external_id": { + "name": "bot_external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bot_username": { + "name": "bot_username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bot_display_name": { + "name": "bot_display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bot_avatar_url": { + "name": "bot_avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "allow_direct_messages": { + "name": "allow_direct_messages", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "allow_group_chats": { + "name": "allow_group_chats", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "allow_unlinked_people": { + "name": "allow_unlinked_people", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "concurrency_policy": { + "name": "concurrency_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queue'" + }, + "capabilities": { + "name": "capabilities", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"threads\":false,\"directMessages\":false,\"nativeStreaming\":false,\"messageEdits\":false,\"messageDeletes\":false,\"reactions\":false,\"files\":false,\"cards\":false,\"actions\":false,\"modals\":false,\"slashCommands\":false,\"ephemeralMessages\":false,\"proactiveDirectMessages\":false}'::jsonb" + }, + "setup": { + "name": "setup", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"step\":\"provider_setup\"}'::jsonb" + }, + "health_message": { + "name": "health_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_event_at": { + "name": "last_event_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_publication_at": { + "name": "last_publication_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "activated_at": { + "name": "activated_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": { + "chat_endpoints_company_idx": { + "name": "chat_endpoints_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_agent_idx": { + "name": "chat_endpoints_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assigned_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_status_idx": { + "name": "chat_endpoints_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_public_id_uq": { + "name": "chat_endpoints_public_id_uq", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_agentmail_inbox_uq": { + "name": "chat_endpoints_agentmail_inbox_uq", + "columns": [ + { + "expression": "bot_external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat_endpoints\".\"provider\" = 'agentmail' and \"chat_endpoints\".\"status\" != 'archived' and \"chat_endpoints\".\"bot_external_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_connection_uq": { + "name": "chat_endpoints_connection_uq", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_live_bot_external_uq": { + "name": "chat_endpoints_live_bot_external_uq", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bot_external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat_endpoints\".\"status\" in ('verifying', 'active', 'paused', 'attention')\n and \"chat_endpoints\".\"provider_account_id\" is not null\n and \"chat_endpoints\".\"bot_external_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_photon_number_uq": { + "name": "chat_endpoints_photon_number_uq", + "columns": [ + { + "expression": "bot_external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat_endpoints\".\"provider\" = 'imessage-photon' and \"chat_endpoints\".\"status\" <> 'archived' and \"chat_endpoints\".\"bot_external_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_live_discord_bot_external_uq": { + "name": "chat_endpoints_live_discord_bot_external_uq", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bot_external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat_endpoints\".\"provider\" = 'discord'\n and \"chat_endpoints\".\"status\" in ('verifying', 'active', 'paused', 'attention')\n and \"chat_endpoints\".\"bot_external_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_live_global_app_bot_external_uq": { + "name": "chat_endpoints_live_global_app_bot_external_uq", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bot_external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat_endpoints\".\"provider\" in ('github', 'microsoft-teams')\n and \"chat_endpoints\".\"status\" in ('verifying', 'active', 'paused', 'attention')\n and \"chat_endpoints\".\"bot_external_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_endpoints_live_bot_username_uq": { + "name": "chat_endpoints_live_bot_username_uq", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bot_username", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat_endpoints\".\"status\" in ('verifying', 'active', 'paused', 'attention')\n and \"chat_endpoints\".\"provider_account_id\" is not null\n and \"chat_endpoints\".\"bot_username\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_endpoints_company_id_companies_id_fk": { + "name": "chat_endpoints_company_id_companies_id_fk", + "tableFrom": "chat_endpoints", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_endpoints_assigned_agent_id_agents_id_fk": { + "name": "chat_endpoints_assigned_agent_id_agents_id_fk", + "tableFrom": "chat_endpoints", + "tableTo": "agents", + "columnsFrom": [ + "assigned_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "chat_endpoints_company_agent_fk": { + "name": "chat_endpoints_company_agent_fk", + "tableFrom": "chat_endpoints", + "tableTo": "agents", + "columnsFrom": [ + "company_id", + "assigned_agent_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_endpoints_company_connection_fk": { + "name": "chat_endpoints_company_connection_fk", + "tableFrom": "chat_endpoints", + "tableTo": "tool_connections", + "columnsFrom": [ + "company_id", + "connection_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_endpoints_company_id_uq": { + "name": "chat_endpoints_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "chat_endpoints_publication_mode_check": { + "name": "chat_endpoints_publication_mode_check", + "value": "\"chat_endpoints\".\"publication_mode\" in ('automatic', 'explicit')" + }, + "chat_endpoints_execution_policy_check": { + "name": "chat_endpoints_execution_policy_check", + "value": "\"chat_endpoints\".\"external_execution_policy\" in ('restricted', 'agent')" + }, + "chat_endpoints_email_policy_check": { + "name": "chat_endpoints_email_policy_check", + "value": "\"chat_endpoints\".\"provider\" <> 'agentmail' or (\"chat_endpoints\".\"publication_mode\" = 'explicit' and \"chat_endpoints\".\"external_execution_policy\" = 'agent')" + }, + "chat_endpoints_provider_check": { + "name": "chat_endpoints_provider_check", + "value": "\"chat_endpoints\".\"provider\" in ('slack', 'github', 'discord', 'microsoft-teams', 'telegram', 'agentmail', 'imessage-photon')" + }, + "chat_endpoints_status_check": { + "name": "chat_endpoints_status_check", + "value": "\"chat_endpoints\".\"status\" in ('draft', 'verifying', 'active', 'paused', 'attention', 'revoked', 'archived')" + }, + "chat_endpoints_deployment_check": { + "name": "chat_endpoints_deployment_check", + "value": "\"chat_endpoints\".\"deployment_mode\" in ('direct', 'relay')" + }, + "chat_endpoints_concurrency_check": { + "name": "chat_endpoints_concurrency_check", + "value": "\"chat_endpoints\".\"concurrency_policy\" in ('burst', 'queue', 'debounce', 'drop', 'concurrent')" + } + }, + "isRLSEnabled": false + }, + "public.chat_external_principals": { + "name": "chat_external_principals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_account_id": { + "name": "provider_account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'user'" + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "handle": { + "name": "handle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_bot": { + "name": "is_bot", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_external_principals_company_idx": { + "name": "chat_external_principals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_external_principals_external_uq": { + "name": "chat_external_principals_external_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_external_principals_company_id_companies_id_fk": { + "name": "chat_external_principals_company_id_companies_id_fk", + "tableFrom": "chat_external_principals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_external_principals_company_id_uq": { + "name": "chat_external_principals_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "chat_external_principals_provider_check": { + "name": "chat_external_principals_provider_check", + "value": "\"chat_external_principals\".\"provider\" in ('slack', 'github', 'discord', 'microsoft-teams', 'telegram', 'agentmail', 'imessage-photon')" + }, + "chat_external_principals_kind_check": { + "name": "chat_external_principals_kind_check", + "value": "\"chat_external_principals\".\"kind\" in ('user', 'bot', 'app', 'system')" + } + }, + "isRLSEnabled": false + }, + "public.chat_identity_links": { + "name": "chat_identity_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "paperclip_user_id": { + "name": "paperclip_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "confirmation_token_hash": { + "name": "confirmation_token_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "confirmed_at": { + "name": "confirmed_at", + "type": "timestamp with time zone", + "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": { + "chat_identity_links_user_idx": { + "name": "chat_identity_links_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "paperclip_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_identity_links_endpoint_principal_uq": { + "name": "chat_identity_links_endpoint_principal_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_identity_links_company_id_companies_id_fk": { + "name": "chat_identity_links_company_id_companies_id_fk", + "tableFrom": "chat_identity_links", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_identity_links_company_endpoint_fk": { + "name": "chat_identity_links_company_endpoint_fk", + "tableFrom": "chat_identity_links", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_identity_links_company_principal_fk": { + "name": "chat_identity_links_company_principal_fk", + "tableFrom": "chat_identity_links", + "tableTo": "chat_external_principals", + "columnsFrom": [ + "company_id", + "principal_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_identity_links_status_check": { + "name": "chat_identity_links_status_check", + "value": "\"chat_identity_links\".\"status\" in ('pending', 'linked', 'revoked', 'expired')" + } + }, + "isRLSEnabled": false + }, + "public.chat_message_links": { + "name": "chat_message_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "delivery_id": { + "name": "delivery_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "publication_id": { + "name": "publication_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "comment_id": { + "name": "comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider_message_id": { + "name": "provider_message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_message_links_provider_message_uq": { + "name": "chat_message_links_provider_message_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_message_links_company_id_companies_id_fk": { + "name": "chat_message_links_company_id_companies_id_fk", + "tableFrom": "chat_message_links", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_message_links_delivery_id_chat_deliveries_id_fk": { + "name": "chat_message_links_delivery_id_chat_deliveries_id_fk", + "tableFrom": "chat_message_links", + "tableTo": "chat_deliveries", + "columnsFrom": [ + "delivery_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_message_links_publication_id_chat_publications_id_fk": { + "name": "chat_message_links_publication_id_chat_publications_id_fk", + "tableFrom": "chat_message_links", + "tableTo": "chat_publications", + "columnsFrom": [ + "publication_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_message_links_comment_id_issue_comments_id_fk": { + "name": "chat_message_links_comment_id_issue_comments_id_fk", + "tableFrom": "chat_message_links", + "tableTo": "issue_comments", + "columnsFrom": [ + "comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_message_links_company_endpoint_fk": { + "name": "chat_message_links_company_endpoint_fk", + "tableFrom": "chat_message_links", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_message_links_company_delivery_fk": { + "name": "chat_message_links_company_delivery_fk", + "tableFrom": "chat_message_links", + "tableTo": "chat_deliveries", + "columnsFrom": [ + "company_id", + "delivery_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_message_links_company_publication_fk": { + "name": "chat_message_links_company_publication_fk", + "tableFrom": "chat_message_links", + "tableTo": "chat_publications", + "columnsFrom": [ + "company_id", + "publication_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_message_links_company_comment_fk": { + "name": "chat_message_links_company_comment_fk", + "tableFrom": "chat_message_links", + "tableTo": "issue_comments", + "columnsFrom": [ + "company_id", + "comment_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_message_links_company_conversation_fk": { + "name": "chat_message_links_company_conversation_fk", + "tableFrom": "chat_message_links", + "tableTo": "chat_conversations", + "columnsFrom": [ + "company_id", + "conversation_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_message_links_direction_check": { + "name": "chat_message_links_direction_check", + "value": "\"chat_message_links\".\"direction\" in ('inbound', 'outbound')" + } + }, + "isRLSEnabled": false + }, + "public.chat_publications": { + "name": "chat_publications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "comment_id": { + "name": "comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "provider_message_id": { + "name": "provider_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_url": { + "name": "provider_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "redacted_error": { + "name": "redacted_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "published_at": { + "name": "published_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": { + "chat_publications_company_id_uq": { + "name": "chat_publications_company_id_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_publications_work_idx": { + "name": "chat_publications_work_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_publications_idempotency_uq": { + "name": "chat_publications_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_publications_company_id_companies_id_fk": { + "name": "chat_publications_company_id_companies_id_fk", + "tableFrom": "chat_publications", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_publications_issue_id_issues_id_fk": { + "name": "chat_publications_issue_id_issues_id_fk", + "tableFrom": "chat_publications", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "chat_publications_comment_id_issue_comments_id_fk": { + "name": "chat_publications_comment_id_issue_comments_id_fk", + "tableFrom": "chat_publications", + "tableTo": "issue_comments", + "columnsFrom": [ + "comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_publications_company_issue_fk": { + "name": "chat_publications_company_issue_fk", + "tableFrom": "chat_publications", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_publications_company_comment_fk": { + "name": "chat_publications_company_comment_fk", + "tableFrom": "chat_publications", + "tableTo": "issue_comments", + "columnsFrom": [ + "company_id", + "comment_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_publications_company_endpoint_fk": { + "name": "chat_publications_company_endpoint_fk", + "tableFrom": "chat_publications", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_publications_company_conversation_fk": { + "name": "chat_publications_company_conversation_fk", + "tableFrom": "chat_publications", + "tableTo": "chat_conversations", + "columnsFrom": [ + "company_id", + "conversation_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_publications_state_check": { + "name": "chat_publications_state_check", + "value": "\"chat_publications\".\"state\" in ('pending', 'streaming', 'published', 'retry', 'delivery_unknown', 'failed', 'cancelled', 'awaiting_consent')" + } + }, + "isRLSEnabled": false + }, + "public.chat_sdk_state": { + "name": "chat_sdk_state", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "state_key": { + "name": "state_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_sdk_state_key_uq": { + "name": "chat_sdk_state_key_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "state_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_sdk_state_expiry_idx": { + "name": "chat_sdk_state_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_sdk_state_company_id_companies_id_fk": { + "name": "chat_sdk_state_company_id_companies_id_fk", + "tableFrom": "chat_sdk_state", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_sdk_state_company_endpoint_fk": { + "name": "chat_sdk_state_company_endpoint_fk", + "tableFrom": "chat_sdk_state", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_discord_command_owners": { + "name": "chat_discord_command_owners", + "schema": "", + "columns": { + "application_id": { + "name": "application_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "action_id": { + "name": "action_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_discord_command_owners_application_check": { + "name": "chat_discord_command_owners_application_check", + "value": "\"chat_discord_command_owners\".\"application_id\" ~ '^[1-9][0-9]{16,19}$'" + } + }, + "isRLSEnabled": false + }, + "public.chat_teams_file_transfers": { + "name": "chat_teams_file_transfers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "publication_id": { + "name": "publication_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "comment_id": { + "name": "comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "attachment_id": { + "name": "attachment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "authorized_user_id": { + "name": "authorized_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "runtime_generation": { + "name": "runtime_generation", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "credential_fingerprint": { + "name": "credential_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "conversation_generation": { + "name": "conversation_generation", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "source_digest": { + "name": "source_digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "authority_digest": { + "name": "authority_digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "bot_app_id": { + "name": "bot_app_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "aad_object_id": { + "name": "aad_object_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider_conversation_id": { + "name": "provider_conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_user_id": { + "name": "provider_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sha256": { + "name": "sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "byte_size": { + "name": "byte_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "filename": { + "name": "filename", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_sha256": { + "name": "token_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'consent_pending'" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "attempt_id": { + "name": "attempt_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "attempt_expires_at": { + "name": "attempt_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "consent_message_id": { + "name": "consent_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "file_info_message_id": { + "name": "file_info_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "response_activity_id": { + "name": "response_activity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "response_digest": { + "name": "response_digest", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "private_state": { + "name": "private_state", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_teams_file_transfers_publication_uq": { + "name": "chat_teams_file_transfers_publication_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "publication_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_teams_file_transfers_token_uq": { + "name": "chat_teams_file_transfers_token_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "token_sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_teams_file_transfers_work_idx": { + "name": "chat_teams_file_transfers_work_idx", + "columns": [ + { + "expression": "phase", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "attempt_expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_teams_file_transfers_company_id_companies_id_fk": { + "name": "chat_teams_file_transfers_company_id_companies_id_fk", + "tableFrom": "chat_teams_file_transfers", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_teams_file_transfers_issue_id_issues_id_fk": { + "name": "chat_teams_file_transfers_issue_id_issues_id_fk", + "tableFrom": "chat_teams_file_transfers", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "chat_teams_file_transfers_company_id_endpoint_id_chat_endpoints_company_id_id_fk": { + "name": "chat_teams_file_transfers_company_id_endpoint_id_chat_endpoints_company_id_id_fk", + "tableFrom": "chat_teams_file_transfers", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_teams_file_transfers_company_id_publication_id_chat_publications_company_id_id_fk": { + "name": "chat_teams_file_transfers_company_id_publication_id_chat_publications_company_id_id_fk", + "tableFrom": "chat_teams_file_transfers", + "tableTo": "chat_publications", + "columnsFrom": [ + "company_id", + "publication_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_teams_file_transfers_company_id_conversation_id_chat_conversations_company_id_id_fk": { + "name": "chat_teams_file_transfers_company_id_conversation_id_chat_conversations_company_id_id_fk", + "tableFrom": "chat_teams_file_transfers", + "tableTo": "chat_conversations", + "columnsFrom": [ + "company_id", + "conversation_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_teams_file_transfers_company_id_principal_id_chat_external_principals_company_id_id_fk": { + "name": "chat_teams_file_transfers_company_id_principal_id_chat_external_principals_company_id_id_fk", + "tableFrom": "chat_teams_file_transfers", + "tableTo": "chat_external_principals", + "columnsFrom": [ + "company_id", + "principal_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_teams_file_transfers_phase_check": { + "name": "chat_teams_file_transfers_phase_check", + "value": "\"chat_teams_file_transfers\".\"phase\" in ('consent_pending','consent_sending','consent_unknown','awaiting_consent','upload_pending','uploading','upload_unknown','file_info_pending','file_info_sending','file_info_unknown','delivered','declined','expired','cancelled','conflict')" + }, + "chat_teams_file_transfers_bounds_check": { + "name": "chat_teams_file_transfers_bounds_check", + "value": "\"chat_teams_file_transfers\".\"version\" > 0 and \"chat_teams_file_transfers\".\"runtime_generation\" >= 0 and \"chat_teams_file_transfers\".\"conversation_generation\" > 0 and \"chat_teams_file_transfers\".\"byte_size\" > 0 and \"chat_teams_file_transfers\".\"byte_size\" < 62914560" + }, + "chat_teams_file_transfers_hash_check": { + "name": "chat_teams_file_transfers_hash_check", + "value": "\"chat_teams_file_transfers\".\"source_digest\" ~ '^[a-f0-9]{64}$' and \"chat_teams_file_transfers\".\"authority_digest\" ~ '^[a-f0-9]{64}$' and \"chat_teams_file_transfers\".\"sha256\" ~ '^[a-f0-9]{64}$' and \"chat_teams_file_transfers\".\"token_sha256\" ~ '^[a-f0-9]{64}$'" + }, + "chat_teams_file_transfers_attempt_check": { + "name": "chat_teams_file_transfers_attempt_check", + "value": "(\"chat_teams_file_transfers\".\"attempt_id\" is null) = (\"chat_teams_file_transfers\".\"attempt_expires_at\" is null)" + } + }, + "isRLSEnabled": false + }, + "public.cli_auth_challenges": { + "name": "cli_auth_challenges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "secret_hash": { + "name": "secret_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_access": { + "name": "requested_access", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'board'" + }, + "requested_company_id": { + "name": "requested_company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "pending_key_hash": { + "name": "pending_key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pending_key_name": { + "name": "pending_key_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "approved_by_user_id": { + "name": "approved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "board_api_key_id": { + "name": "board_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approved_at": { + "name": "approved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cli_auth_challenges_secret_hash_idx": { + "name": "cli_auth_challenges_secret_hash_idx", + "columns": [ + { + "expression": "secret_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cli_auth_challenges_approved_by_idx": { + "name": "cli_auth_challenges_approved_by_idx", + "columns": [ + { + "expression": "approved_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cli_auth_challenges_requested_company_idx": { + "name": "cli_auth_challenges_requested_company_idx", + "columns": [ + { + "expression": "requested_company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cli_auth_challenges_requested_company_id_companies_id_fk": { + "name": "cli_auth_challenges_requested_company_id_companies_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "companies", + "columnsFrom": [ + "requested_company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cli_auth_challenges_approved_by_user_id_user_id_fk": { + "name": "cli_auth_challenges_approved_by_user_id_user_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "user", + "columnsFrom": [ + "approved_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cli_auth_challenges_board_api_key_id_board_api_keys_id_fk": { + "name": "cli_auth_challenges_board_api_key_id_board_api_keys_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "board_api_keys", + "columnsFrom": [ + "board_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.companies": { + "name": "companies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "issue_prefix": { + "name": "issue_prefix", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'PAP'" + }, + "issue_counter": { + "name": "issue_counter", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "budget_monthly_cents": { + "name": "budget_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "spent_monthly_cents": { + "name": "spent_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "default_responsible_user_id": { + "name": "default_responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "require_board_approval_for_new_agents": { + "name": "require_board_approval_for_new_agents", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "interaction_resolver_governance": { + "name": "interaction_resolver_governance", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "feedback_data_sharing_enabled": { + "name": "feedback_data_sharing_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "feedback_data_sharing_consent_at": { + "name": "feedback_data_sharing_consent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "feedback_data_sharing_consent_by_user_id": { + "name": "feedback_data_sharing_consent_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feedback_data_sharing_terms_version": { + "name": "feedback_data_sharing_terms_version", + "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": { + "companies_issue_prefix_idx": { + "name": "companies_issue_prefix_idx", + "columns": [ + { + "expression": "issue_prefix", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_logos": { + "name": "company_logos", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_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": { + "company_logos_company_uq": { + "name": "company_logos_company_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_logos_asset_uq": { + "name": "company_logos_asset_uq", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_logos_company_id_companies_id_fk": { + "name": "company_logos_company_id_companies_id_fk", + "tableFrom": "company_logos", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_logos_asset_id_assets_id_fk": { + "name": "company_logos_asset_id_assets_id_fk", + "tableFrom": "company_logos", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_memberships": { + "name": "company_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "principal_type": { + "name": "principal_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "membership_role": { + "name": "membership_role", + "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": { + "company_memberships_company_principal_unique_idx": { + "name": "company_memberships_company_principal_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_memberships_principal_status_idx": { + "name": "company_memberships_principal_status_idx", + "columns": [ + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_memberships_company_status_idx": { + "name": "company_memberships_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_memberships_company_id_companies_id_fk": { + "name": "company_memberships_company_id_companies_id_fk", + "tableFrom": "company_memberships", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_onboarding_seeds": { + "name": "company_onboarding_seeds", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mission": { + "name": "mission", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_name": { + "name": "agent_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_role": { + "name": "agent_role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "first_task_title": { + "name": "first_task_title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "first_task_details": { + "name": "first_task_details", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "applied_at": { + "name": "applied_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_onboarding_seeds_company_uq": { + "name": "company_onboarding_seeds_company_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_onboarding_seeds_company_id_companies_id_fk": { + "name": "company_onboarding_seeds_company_id_companies_id_fk", + "tableFrom": "company_onboarding_seeds", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_onboarding_seeds_goal_id_goals_id_fk": { + "name": "company_onboarding_seeds_goal_id_goals_id_fk", + "tableFrom": "company_onboarding_seeds", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_onboarding_seeds_agent_id_agents_id_fk": { + "name": "company_onboarding_seeds_agent_id_agents_id_fk", + "tableFrom": "company_onboarding_seeds", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_onboarding_seeds_issue_id_issues_id_fk": { + "name": "company_onboarding_seeds_issue_id_issues_id_fk", + "tableFrom": "company_onboarding_seeds", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secret_bindings": { + "name": "company_secret_bindings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "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 + }, + "config_path": { + "name": "config_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version_selector": { + "name": "version_selector", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'latest'" + }, + "required": { + "name": "required", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "projection_class": { + "name": "projection_class", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unclassified'" + }, + "projection_allowlist_key": { + "name": "projection_allowlist_key", + "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": { + "company_secret_bindings_company_idx": { + "name": "company_secret_bindings_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_bindings_secret_idx": { + "name": "company_secret_bindings_secret_idx", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_bindings_target_idx": { + "name": "company_secret_bindings_target_idx", + "columns": [ + { + "expression": "company_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": {} + }, + "company_secret_bindings_target_path_uq": { + "name": "company_secret_bindings_target_path_uq", + "columns": [ + { + "expression": "company_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" + }, + { + "expression": "config_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secret_bindings_company_id_companies_id_fk": { + "name": "company_secret_bindings_company_id_companies_id_fk", + "tableFrom": "company_secret_bindings", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "company_secret_bindings_secret_id_company_secrets_id_fk": { + "name": "company_secret_bindings_secret_id_company_secrets_id_fk", + "tableFrom": "company_secret_bindings", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secret_proposals": { + "name": "company_secret_proposals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "proposed_name": { + "name": "proposed_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "proposed_key": { + "name": "proposed_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "proposed_description": { + "name": "proposed_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "justification": { + "name": "justification", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value_ciphertext": { + "name": "value_ciphertext", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "value_fingerprint_sha256": { + "name": "value_fingerprint_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "value_length": { + "name": "value_length", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "secret_proposal_id": { + "name": "secret_proposal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_id": { + "name": "target_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "config_path": { + "name": "config_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "projection_class": { + "name": "projection_class", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unclassified'" + }, + "binding_target_policy_snapshot": { + "name": "binding_target_policy_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "proposer_ancestor_ids_snapshot": { + "name": "proposer_ancestor_ids_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "target_ancestor_ids_snapshot": { + "name": "target_ancestor_ids_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "proposed_by_agent_id": { + "name": "proposed_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "origin_issue_id": { + "name": "origin_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "origin_run_id": { + "name": "origin_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "interaction_id": { + "name": "interaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_by_user_id": { + "name": "resolved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "resolution_reason": { + "name": "resolution_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_secret_id": { + "name": "created_secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "applied_binding_config_path": { + "name": "applied_binding_config_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ciphertext_scrubbed_at": { + "name": "ciphertext_scrubbed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_secret_proposals_company_status_idx": { + "name": "company_secret_proposals_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_proposals_proposer_status_idx": { + "name": "company_secret_proposals_proposer_status_idx", + "columns": [ + { + "expression": "proposed_by_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_proposals_expiry_idx": { + "name": "company_secret_proposals_expiry_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_proposals_secret_proposal_idx": { + "name": "company_secret_proposals_secret_proposal_idx", + "columns": [ + { + "expression": "secret_proposal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_proposals_interaction_idx": { + "name": "company_secret_proposals_interaction_idx", + "columns": [ + { + "expression": "interaction_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secret_proposals_company_id_companies_id_fk": { + "name": "company_secret_proposals_company_id_companies_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "company_secret_proposals_secret_id_company_secrets_id_fk": { + "name": "company_secret_proposals_secret_id_company_secrets_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_secret_proposals_secret_proposal_id_company_secret_proposals_id_fk": { + "name": "company_secret_proposals_secret_proposal_id_company_secret_proposals_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "company_secret_proposals", + "columnsFrom": [ + "secret_proposal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_proposals_target_id_agents_id_fk": { + "name": "company_secret_proposals_target_id_agents_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "agents", + "columnsFrom": [ + "target_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_proposals_proposed_by_agent_id_agents_id_fk": { + "name": "company_secret_proposals_proposed_by_agent_id_agents_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "agents", + "columnsFrom": [ + "proposed_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_proposals_origin_issue_id_issues_id_fk": { + "name": "company_secret_proposals_origin_issue_id_issues_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "issues", + "columnsFrom": [ + "origin_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_secret_proposals_origin_run_id_heartbeat_runs_id_fk": { + "name": "company_secret_proposals_origin_run_id_heartbeat_runs_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "origin_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_proposals_interaction_id_issue_thread_interactions_id_fk": { + "name": "company_secret_proposals_interaction_id_issue_thread_interactions_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "issue_thread_interactions", + "columnsFrom": [ + "interaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_secret_proposals_created_secret_id_company_secrets_id_fk": { + "name": "company_secret_proposals_created_secret_id_company_secrets_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "company_secrets", + "columnsFrom": [ + "created_secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "company_secret_proposals_kind_check": { + "name": "company_secret_proposals_kind_check", + "value": "\"company_secret_proposals\".\"kind\" in ('secret', 'binding')" + }, + "company_secret_proposals_status_check": { + "name": "company_secret_proposals_status_check", + "value": "\"company_secret_proposals\".\"status\" in ('pending', 'approved', 'rejected', 'withdrawn', 'expired')" + }, + "company_secret_proposals_projection_check": { + "name": "company_secret_proposals_projection_check", + "value": "\"company_secret_proposals\".\"projection_class\" = 'unclassified'" + }, + "company_secret_proposals_shape_check": { + "name": "company_secret_proposals_shape_check", + "value": "(\n \"company_secret_proposals\".\"kind\" = 'secret'\n and \"company_secret_proposals\".\"proposed_name\" is not null\n and \"company_secret_proposals\".\"proposed_key\" is not null\n and \"company_secret_proposals\".\"secret_id\" is null\n and \"company_secret_proposals\".\"secret_proposal_id\" is null\n and \"company_secret_proposals\".\"target_type\" is null\n and \"company_secret_proposals\".\"target_id\" is null\n and \"company_secret_proposals\".\"config_path\" is null\n ) or (\n \"company_secret_proposals\".\"kind\" = 'binding'\n and ((\"company_secret_proposals\".\"secret_id\" is not null)::int + (\"company_secret_proposals\".\"secret_proposal_id\" is not null)::int) = 1\n and \"company_secret_proposals\".\"target_type\" = 'agent'\n and \"company_secret_proposals\".\"target_id\" is not null\n and \"company_secret_proposals\".\"config_path\" is not null\n )" + } + }, + "isRLSEnabled": false + }, + "public.company_secret_provider_configs": { + "name": "company_secret_provider_configs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "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, + "default": "'ready'" + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "health_checked_at": { + "name": "health_checked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "health_message": { + "name": "health_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "health_details": { + "name": "health_details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "disabled_at": { + "name": "disabled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_secret_provider_configs_company_idx": { + "name": "company_secret_provider_configs_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_provider_configs_company_provider_idx": { + "name": "company_secret_provider_configs_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_provider_configs_default_uq": { + "name": "company_secret_provider_configs_default_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"company_secret_provider_configs\".\"is_default\" = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secret_provider_configs_company_id_companies_id_fk": { + "name": "company_secret_provider_configs_company_id_companies_id_fk", + "tableFrom": "company_secret_provider_configs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_provider_configs_created_by_agent_id_agents_id_fk": { + "name": "company_secret_provider_configs_created_by_agent_id_agents_id_fk", + "tableFrom": "company_secret_provider_configs", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secret_versions": { + "name": "company_secret_versions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "material": { + "name": "material", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "value_sha256": { + "name": "value_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_version_ref": { + "name": "provider_version_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'current'" + }, + "fingerprint_sha256": { + "name": "fingerprint_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rotation_job_id": { + "name": "rotation_job_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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": { + "company_secret_versions_secret_idx": { + "name": "company_secret_versions_secret_idx", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_versions_value_sha256_idx": { + "name": "company_secret_versions_value_sha256_idx", + "columns": [ + { + "expression": "value_sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_versions_fingerprint_idx": { + "name": "company_secret_versions_fingerprint_idx", + "columns": [ + { + "expression": "fingerprint_sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_versions_secret_version_uq": { + "name": "company_secret_versions_secret_version_uq", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secret_versions_secret_id_company_secrets_id_fk": { + "name": "company_secret_versions_secret_id_company_secrets_id_fk", + "tableFrom": "company_secret_versions", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_versions_created_by_agent_id_agents_id_fk": { + "name": "company_secret_versions_created_by_agent_id_agents_id_fk", + "tableFrom": "company_secret_versions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secrets": { + "name": "company_secrets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company'" + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_secret_definition_id": { + "name": "user_secret_definition_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_encrypted'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "managed_mode": { + "name": "managed_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip_managed'" + }, + "external_ref": { + "name": "external_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_config_id": { + "name": "provider_config_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider_metadata": { + "name": "provider_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "latest_version": { + "name": "latest_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_resolved_at": { + "name": "last_resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_rotated_at": { + "name": "last_rotated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_secrets_company_idx": { + "name": "company_secrets_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_scope_idx": { + "name": "company_secrets_company_scope_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_owner_idx": { + "name": "company_secrets_company_owner_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_user_definition_owner_idx": { + "name": "company_secrets_user_definition_owner_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_secret_definition_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_provider_idx": { + "name": "company_secrets_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_provider_config_idx": { + "name": "company_secrets_provider_config_idx", + "columns": [ + { + "expression": "provider_config_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_name_uq": { + "name": "company_secrets_company_name_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"company_secrets\".\"scope\" = 'company' and \"company_secrets\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_key_uq": { + "name": "company_secrets_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"company_secrets\".\"scope\" = 'company' and \"company_secrets\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_user_definition_owner_uq": { + "name": "company_secrets_user_definition_owner_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_secret_definition_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"company_secrets\".\"scope\" = 'user' and \"company_secrets\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secrets_company_id_companies_id_fk": { + "name": "company_secrets_company_id_companies_id_fk", + "tableFrom": "company_secrets", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "company_secrets_user_secret_definition_id_user_secret_definitions_id_fk": { + "name": "company_secrets_user_secret_definition_id_user_secret_definitions_id_fk", + "tableFrom": "company_secrets", + "tableTo": "user_secret_definitions", + "columnsFrom": [ + "user_secret_definition_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_secrets_provider_config_id_company_secret_provider_configs_id_fk": { + "name": "company_secrets_provider_config_id_company_secret_provider_configs_id_fk", + "tableFrom": "company_secrets", + "tableTo": "company_secret_provider_configs", + "columnsFrom": [ + "provider_config_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_secrets_created_by_agent_id_agents_id_fk": { + "name": "company_secrets_created_by_agent_id_agents_id_fk", + "tableFrom": "company_secrets", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "company_secrets_scope_shape_check": { + "name": "company_secrets_scope_shape_check", + "value": "(\n \"company_secrets\".\"scope\" = 'company'\n and \"company_secrets\".\"owner_user_id\" is null\n and \"company_secrets\".\"user_secret_definition_id\" is null\n ) or (\n \"company_secrets\".\"scope\" = 'user'\n and \"company_secrets\".\"owner_user_id\" is not null\n and \"company_secrets\".\"user_secret_definition_id\" is not null\n )" + } + }, + "isRLSEnabled": false + }, + "public.company_skill_policies": { + "name": "company_skill_policies", + "schema": "", + "columns": { + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "schema_version": { + "name": "schema_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "default_effect": { + "name": "default_effect", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rules": { + "name": "rules", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "company_skill_policies_company_id_companies_id_fk": { + "name": "company_skill_policies_company_id_companies_id_fk", + "tableFrom": "company_skill_policies", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_comments": { + "name": "company_skill_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_skill_id": { + "name": "company_skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "parent_comment_id": { + "name": "parent_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_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": { + "company_skill_comments_company_skill_created_idx": { + "name": "company_skill_comments_company_skill_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_comments_parent_idx": { + "name": "company_skill_comments_parent_idx", + "columns": [ + { + "expression": "parent_comment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_comments_company_id_companies_id_fk": { + "name": "company_skill_comments_company_id_companies_id_fk", + "tableFrom": "company_skill_comments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_comments_company_skill_id_company_skills_id_fk": { + "name": "company_skill_comments_company_skill_id_company_skills_id_fk", + "tableFrom": "company_skill_comments", + "tableTo": "company_skills", + "columnsFrom": [ + "company_skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_comments_parent_comment_id_company_skill_comments_id_fk": { + "name": "company_skill_comments_parent_comment_id_company_skill_comments_id_fk", + "tableFrom": "company_skill_comments", + "tableTo": "company_skill_comments", + "columnsFrom": [ + "parent_comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_skill_comments_author_agent_id_agents_id_fk": { + "name": "company_skill_comments_author_agent_id_agents_id_fk", + "tableFrom": "company_skill_comments", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_stars": { + "name": "company_skill_stars", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_skill_id": { + "name": "company_skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skill_stars_skill_agent_idx": { + "name": "company_skill_stars_skill_agent_idx", + "columns": [ + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_stars_skill_user_idx": { + "name": "company_skill_stars_skill_user_idx", + "columns": [ + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_stars_company_skill_created_idx": { + "name": "company_skill_stars_company_skill_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_stars_company_id_companies_id_fk": { + "name": "company_skill_stars_company_id_companies_id_fk", + "tableFrom": "company_skill_stars", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_stars_company_skill_id_company_skills_id_fk": { + "name": "company_skill_stars_company_skill_id_company_skills_id_fk", + "tableFrom": "company_skill_stars", + "tableTo": "company_skills", + "columnsFrom": [ + "company_skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_stars_agent_id_agents_id_fk": { + "name": "company_skill_stars_agent_id_agents_id_fk", + "tableFrom": "company_skill_stars", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_test_inputs": { + "name": "company_skill_test_inputs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "skill_id": { + "name": "skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_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": { + "company_skill_test_inputs_company_skill_name_idx": { + "name": "company_skill_test_inputs_company_skill_name_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_test_inputs_company_skill_active_idx": { + "name": "company_skill_test_inputs_company_skill_active_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_test_inputs_company_id_companies_id_fk": { + "name": "company_skill_test_inputs_company_id_companies_id_fk", + "tableFrom": "company_skill_test_inputs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_test_inputs_skill_id_company_skills_id_fk": { + "name": "company_skill_test_inputs_skill_id_company_skills_id_fk", + "tableFrom": "company_skill_test_inputs", + "tableTo": "company_skills", + "columnsFrom": [ + "skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_test_run_templates": { + "name": "company_skill_test_run_templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_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": { + "company_skill_test_run_templates_company_active_idx": { + "name": "company_skill_test_run_templates_company_active_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_test_run_templates_company_id_companies_id_fk": { + "name": "company_skill_test_run_templates_company_id_companies_id_fk", + "tableFrom": "company_skill_test_run_templates", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_test_run_templates_created_by_agent_id_agents_id_fk": { + "name": "company_skill_test_run_templates_created_by_agent_id_agents_id_fk", + "tableFrom": "company_skill_test_run_templates", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_skill_test_run_templates_updated_by_agent_id_agents_id_fk": { + "name": "company_skill_test_run_templates_updated_by_agent_id_agents_id_fk", + "tableFrom": "company_skill_test_run_templates", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_test_runs": { + "name": "company_skill_test_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "skill_id": { + "name": "skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "input_id": { + "name": "input_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "input_snapshot": { + "name": "input_snapshot", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "skill_version_id": { + "name": "skill_version_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_config_snapshot": { + "name": "agent_config_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "template_id": { + "name": "template_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "template_name": { + "name": "template_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "template_body": { + "name": "template_body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rendered_template_body": { + "name": "rendered_template_body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "harness_issue_description": { + "name": "harness_issue_description", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "output_document_key": { + "name": "output_document_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'output'" + }, + "output_snapshot": { + "name": "output_snapshot", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "superseded_at": { + "name": "superseded_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "harness_issue_expires_at": { + "name": "harness_issue_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "harness_issue_deleted_at": { + "name": "harness_issue_deleted_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": { + "company_skill_test_runs_company_skill_created_idx": { + "name": "company_skill_test_runs_company_skill_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_test_runs_company_issue_idx": { + "name": "company_skill_test_runs_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_test_runs_company_input_created_idx": { + "name": "company_skill_test_runs_company_input_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "input_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_test_runs_company_status_idx": { + "name": "company_skill_test_runs_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_test_runs_company_harness_expires_idx": { + "name": "company_skill_test_runs_company_harness_expires_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "harness_issue_expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_test_runs_company_id_companies_id_fk": { + "name": "company_skill_test_runs_company_id_companies_id_fk", + "tableFrom": "company_skill_test_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_test_runs_skill_id_company_skills_id_fk": { + "name": "company_skill_test_runs_skill_id_company_skills_id_fk", + "tableFrom": "company_skill_test_runs", + "tableTo": "company_skills", + "columnsFrom": [ + "skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_test_runs_input_id_company_skill_test_inputs_id_fk": { + "name": "company_skill_test_runs_input_id_company_skill_test_inputs_id_fk", + "tableFrom": "company_skill_test_runs", + "tableTo": "company_skill_test_inputs", + "columnsFrom": [ + "input_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_skill_test_runs_skill_version_id_company_skill_versions_id_fk": { + "name": "company_skill_test_runs_skill_version_id_company_skill_versions_id_fk", + "tableFrom": "company_skill_test_runs", + "tableTo": "company_skill_versions", + "columnsFrom": [ + "skill_version_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "company_skill_test_runs_agent_id_agents_id_fk": { + "name": "company_skill_test_runs_agent_id_agents_id_fk", + "tableFrom": "company_skill_test_runs", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "company_skill_test_runs_issue_id_issues_id_fk": { + "name": "company_skill_test_runs_issue_id_issues_id_fk", + "tableFrom": "company_skill_test_runs", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_versions": { + "name": "company_skill_versions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_skill_id": { + "name": "company_skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision_number": { + "name": "revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "release_id": { + "name": "release_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "release_name": { + "name": "release_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "released_at": { + "name": "released_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "file_inventory": { + "name": "file_inventory", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skill_versions_skill_revision_idx": { + "name": "company_skill_versions_skill_revision_idx", + "columns": [ + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_versions_skill_release_idx": { + "name": "company_skill_versions_skill_release_idx", + "columns": [ + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "release_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"company_skill_versions\".\"release_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_versions_company_skill_created_idx": { + "name": "company_skill_versions_company_skill_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_versions_company_id_companies_id_fk": { + "name": "company_skill_versions_company_id_companies_id_fk", + "tableFrom": "company_skill_versions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_versions_company_skill_id_company_skills_id_fk": { + "name": "company_skill_versions_company_skill_id_company_skills_id_fk", + "tableFrom": "company_skill_versions", + "tableTo": "company_skills", + "columnsFrom": [ + "company_skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_versions_author_agent_id_agents_id_fk": { + "name": "company_skill_versions_author_agent_id_agents_id_fk", + "tableFrom": "company_skill_versions", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skills": { + "name": "company_skills", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "folder_id": { + "name": "folder_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "markdown": { + "name": "markdown", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_path'" + }, + "source_locator": { + "name": "source_locator", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_ref": { + "name": "source_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trust_level": { + "name": "trust_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown_only'" + }, + "compatibility": { + "name": "compatibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'compatible'" + }, + "file_inventory": { + "name": "file_inventory", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "icon_url": { + "name": "icon_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tagline": { + "name": "tagline", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "author_name": { + "name": "author_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "homepage_url": { + "name": "homepage_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "categories": { + "name": "categories", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "sharing_scope": { + "name": "sharing_scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company'" + }, + "public_share_token": { + "name": "public_share_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "forked_from_skill_id": { + "name": "forked_from_skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "forked_from_company_id": { + "name": "forked_from_company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "star_count": { + "name": "star_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "install_count": { + "name": "install_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "fork_count": { + "name": "fork_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "current_version_id": { + "name": "current_version_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skills_company_key_idx": { + "name": "company_skills_company_key_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skills_company_name_idx": { + "name": "company_skills_company_name_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skills_company_folder_idx": { + "name": "company_skills_company_folder_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skills_company_categories_idx": { + "name": "company_skills_company_categories_idx", + "columns": [ + { + "expression": "categories", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "company_skills_company_sharing_scope_idx": { + "name": "company_skills_company_sharing_scope_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sharing_scope", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skills_company_current_version_idx": { + "name": "company_skills_company_current_version_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "current_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skills_company_forked_from_idx": { + "name": "company_skills_company_forked_from_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "forked_from_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skills_company_id_companies_id_fk": { + "name": "company_skills_company_id_companies_id_fk", + "tableFrom": "company_skills", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "company_skills_folder_id_folders_id_fk": { + "name": "company_skills_folder_id_folders_id_fk", + "tableFrom": "company_skills", + "tableTo": "folders", + "columnsFrom": [ + "folder_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_skills_forked_from_skill_id_company_skills_id_fk": { + "name": "company_skills_forked_from_skill_id_company_skills_id_fk", + "tableFrom": "company_skills", + "tableTo": "company_skills", + "columnsFrom": [ + "forked_from_skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_skills_forked_from_company_id_companies_id_fk": { + "name": "company_skills_forked_from_company_id_companies_id_fk", + "tableFrom": "company_skills", + "tableTo": "companies", + "columnsFrom": [ + "forked_from_company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_skills_current_version_id_company_skill_versions_id_fk": { + "name": "company_skills_current_version_id_company_skill_versions_id_fk", + "tableFrom": "company_skills", + "tableTo": "company_skill_versions", + "columnsFrom": [ + "current_version_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_transfer_runs": { + "name": "company_transfer_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "actor_key": { + "name": "actor_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "container_ref": { + "name": "container_ref", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "manifest_sha256": { + "name": "manifest_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "manifest": { + "name": "manifest", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "chunk_count": { + "name": "chunk_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "blob_count": { + "name": "blob_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "completed_parts": { + "name": "completed_parts", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_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": { + "company_transfer_runs_company_idx": { + "name": "company_transfer_runs_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_transfer_runs_idempotency_direction_idx": { + "name": "company_transfer_runs_idempotency_direction_idx", + "columns": [ + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "direction", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_transfer_runs_actor_status_idx": { + "name": "company_transfer_runs_actor_status_idx", + "columns": [ + { + "expression": "actor_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_transfer_runs_company_id_companies_id_fk": { + "name": "company_transfer_runs_company_id_companies_id_fk", + "tableFrom": "company_transfer_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_user_sidebar_preferences": { + "name": "company_user_sidebar_preferences", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "project_order": { + "name": "project_order", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_user_sidebar_preferences_company_idx": { + "name": "company_user_sidebar_preferences_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_user_sidebar_preferences_user_idx": { + "name": "company_user_sidebar_preferences_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_user_sidebar_preferences_company_user_uq": { + "name": "company_user_sidebar_preferences_company_user_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_user_sidebar_preferences_company_id_companies_id_fk": { + "name": "company_user_sidebar_preferences_company_id_companies_id_fk", + "tableFrom": "company_user_sidebar_preferences", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.completion_contracts": { + "name": "completion_contracts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "schema_version": { + "name": "schema_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "policy_version": { + "name": "policy_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "risk": { + "name": "risk", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "completion_authority": { + "name": "completion_authority", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "incomplete_criteria_policy": { + "name": "incomplete_criteria_policy", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "contract_json": { + "name": "contract_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "canonical_sha256": { + "name": "canonical_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_actor_type": { + "name": "created_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_actor_id": { + "name": "created_by_actor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "supersedes_contract_id": { + "name": "supersedes_contract_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "completion_contracts_issue_revision_uq": { + "name": "completion_contracts_issue_revision_uq", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "completion_contracts_issue_hash_uq": { + "name": "completion_contracts_issue_hash_uq", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "canonical_sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "completion_contracts_company_id_companies_id_fk": { + "name": "completion_contracts_company_id_companies_id_fk", + "tableFrom": "completion_contracts", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "completion_contracts_issue_company_fk": { + "name": "completion_contracts_issue_company_fk", + "tableFrom": "completion_contracts", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "completion_contracts_supersedes_owner_fk": { + "name": "completion_contracts_supersedes_owner_fk", + "tableFrom": "completion_contracts", + "tableTo": "completion_contracts", + "columnsFrom": [ + "company_id", + "issue_id", + "supersedes_contract_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "completion_contracts_company_issue_id_uq": { + "name": "completion_contracts_company_issue_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "issue_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.connection_event_deliveries": { + "name": "connection_event_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_delivery_id": { + "name": "provider_delivery_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event": { + "name": "event", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "installation_id": { + "name": "installation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repository_id": { + "name": "repository_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "normalized_payload": { + "name": "normalized_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_created_at": { + "name": "provider_created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "processed_at": { + "name": "processed_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": { + "connection_event_deliveries_company_provider_id_uq": { + "name": "connection_event_deliveries_company_provider_id_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_delivery_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_event_deliveries_company_status_idx": { + "name": "connection_event_deliveries_company_status_idx", + "columns": [ + { + "expression": "company_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": { + "connection_event_deliveries_company_id_companies_id_fk": { + "name": "connection_event_deliveries_company_id_companies_id_fk", + "tableFrom": "connection_event_deliveries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.connection_intent_deliveries": { + "name": "connection_intent_deliveries", + "schema": "", + "columns": { + "interaction_id": { + "name": "interaction_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "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()" + } + }, + "indexes": { + "connection_intent_deliveries_pending_idx": { + "name": "connection_intent_deliveries_pending_idx", + "columns": [ + { + "expression": "delivered_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "connection_intent_deliveries_interaction_id_issue_thread_interactions_id_fk": { + "name": "connection_intent_deliveries_interaction_id_issue_thread_interactions_id_fk", + "tableFrom": "connection_intent_deliveries", + "tableTo": "issue_thread_interactions", + "columnsFrom": [ + "interaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_intent_deliveries_company_id_companies_id_fk": { + "name": "connection_intent_deliveries_company_id_companies_id_fk", + "tableFrom": "connection_intent_deliveries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cost_events": { + "name": "cost_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "biller": { + "name": "biller", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "billing_type": { + "name": "billing_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "cost_status": { + "name": "cost_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'reported'" + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "input_tokens": { + "name": "input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cached_input_tokens": { + "name": "cached_input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "output_tokens": { + "name": "output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cost_cents": { + "name": "cost_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "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": { + "cost_events_company_occurred_idx": { + "name": "cost_events_company_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_agent_occurred_idx": { + "name": "cost_events_company_agent_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_provider_occurred_idx": { + "name": "cost_events_company_provider_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_biller_occurred_idx": { + "name": "cost_events_company_biller_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "biller", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_heartbeat_run_idx": { + "name": "cost_events_company_heartbeat_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cost_events_company_id_companies_id_fk": { + "name": "cost_events_company_id_companies_id_fk", + "tableFrom": "cost_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_agent_id_agents_id_fk": { + "name": "cost_events_agent_id_agents_id_fk", + "tableFrom": "cost_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_issue_id_issues_id_fk": { + "name": "cost_events_issue_id_issues_id_fk", + "tableFrom": "cost_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cost_events_project_id_projects_id_fk": { + "name": "cost_events_project_id_projects_id_fk", + "tableFrom": "cost_events", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_goal_id_goals_id_fk": { + "name": "cost_events_goal_id_goals_id_fk", + "tableFrom": "cost_events", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "cost_events_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "cost_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.decision_archive_notification_outbox": { + "name": "decision_archive_notification_outbox", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "archive_version": { + "name": "archive_version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "origin_agent_id": { + "name": "origin_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "origin_issue_id": { + "name": "origin_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "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": { + "decision_archive_notification_outbox_uq": { + "name": "decision_archive_notification_outbox_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archive_version", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_archive_notification_outbox_pending_idx": { + "name": "decision_archive_notification_outbox_pending_idx", + "columns": [ + { + "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": { + "decision_archive_notification_outbox_company_id_companies_id_fk": { + "name": "decision_archive_notification_outbox_company_id_companies_id_fk", + "tableFrom": "decision_archive_notification_outbox", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_archive_notification_outbox_origin_agent_id_agents_id_fk": { + "name": "decision_archive_notification_outbox_origin_agent_id_agents_id_fk", + "tableFrom": "decision_archive_notification_outbox", + "tableTo": "agents", + "columnsFrom": [ + "origin_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "decision_archive_notification_outbox_status_check": { + "name": "decision_archive_notification_outbox_status_check", + "value": "\"decision_archive_notification_outbox\".\"status\" IN ('pending', 'delivering', 'delivered')" + } + }, + "isRLSEnabled": false + }, + "public.decision_queue_items": { + "name": "decision_queue_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "added_by_type": { + "name": "added_by_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "added_by_agent_id": { + "name": "added_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "added_by_user_id": { + "name": "added_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "added_by_run_id": { + "name": "added_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "added_by_agent_api_key_id": { + "name": "added_by_agent_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_queue_items_queue_source_uq": { + "name": "decision_queue_items_queue_source_uq", + "columns": [ + { + "expression": "queue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_queue_items_company_source_idx": { + "name": "decision_queue_items_company_source_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_queue_items_company_id_companies_id_fk": { + "name": "decision_queue_items_company_id_companies_id_fk", + "tableFrom": "decision_queue_items", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_queue_items_added_by_agent_id_agents_id_fk": { + "name": "decision_queue_items_added_by_agent_id_agents_id_fk", + "tableFrom": "decision_queue_items", + "tableTo": "agents", + "columnsFrom": [ + "added_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_queue_items_added_by_run_id_heartbeat_runs_id_fk": { + "name": "decision_queue_items_added_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "decision_queue_items", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "added_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_queue_items_added_by_agent_api_key_id_agent_api_keys_id_fk": { + "name": "decision_queue_items_added_by_agent_api_key_id_agent_api_keys_id_fk", + "tableFrom": "decision_queue_items", + "tableTo": "agent_api_keys", + "columnsFrom": [ + "added_by_agent_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_queue_items_queue_company_fk": { + "name": "decision_queue_items_queue_company_fk", + "tableFrom": "decision_queue_items", + "tableTo": "decision_queues", + "columnsFrom": [ + "queue_id", + "company_id" + ], + "columnsTo": [ + "id", + "company_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "decision_queue_items_actor_check": { + "name": "decision_queue_items_actor_check", + "value": "(\n (\"decision_queue_items\".\"added_by_type\" = 'agent' AND \"decision_queue_items\".\"added_by_agent_id\" IS NOT NULL AND \"decision_queue_items\".\"added_by_user_id\" IS NULL)\n OR (\"decision_queue_items\".\"added_by_type\" = 'user' AND \"decision_queue_items\".\"added_by_agent_id\" IS NULL AND \"decision_queue_items\".\"added_by_user_id\" IS NOT NULL)\n OR (\"decision_queue_items\".\"added_by_type\" = 'system' AND \"decision_queue_items\".\"added_by_agent_id\" IS NULL AND \"decision_queue_items\".\"added_by_user_id\" IS NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.decision_queues": { + "name": "decision_queues", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_type": { + "name": "created_by_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_api_key_id": { + "name": "created_by_agent_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "retention_days": { + "name": "retention_days", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "seed_rules": { + "name": "seed_rules", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "seed_rules_enabled": { + "name": "seed_rules_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": { + "decision_queues_company_key_uq": { + "name": "decision_queues_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_queues_company_updated_idx": { + "name": "decision_queues_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_queues_company_id_companies_id_fk": { + "name": "decision_queues_company_id_companies_id_fk", + "tableFrom": "decision_queues", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_queues_created_by_agent_id_agents_id_fk": { + "name": "decision_queues_created_by_agent_id_agents_id_fk", + "tableFrom": "decision_queues", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_queues_created_by_run_id_heartbeat_runs_id_fk": { + "name": "decision_queues_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "decision_queues", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_queues_created_by_agent_api_key_id_agent_api_keys_id_fk": { + "name": "decision_queues_created_by_agent_api_key_id_agent_api_keys_id_fk", + "tableFrom": "decision_queues", + "tableTo": "agent_api_keys", + "columnsFrom": [ + "created_by_agent_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "decision_queues_id_company_uq": { + "name": "decision_queues_id_company_uq", + "nullsNotDistinct": false, + "columns": [ + "id", + "company_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "decision_queues_creator_check": { + "name": "decision_queues_creator_check", + "value": "(\n (\"decision_queues\".\"created_by_type\" = 'agent' AND \"decision_queues\".\"created_by_agent_id\" IS NOT NULL AND \"decision_queues\".\"created_by_user_id\" IS NULL)\n OR (\"decision_queues\".\"created_by_type\" = 'user' AND \"decision_queues\".\"created_by_agent_id\" IS NULL AND \"decision_queues\".\"created_by_user_id\" IS NOT NULL)\n OR (\"decision_queues\".\"created_by_type\" = 'system' AND \"decision_queues\".\"created_by_agent_id\" IS NULL AND \"decision_queues\".\"created_by_user_id\" IS NULL)\n )" + }, + "decision_queues_retention_days_check": { + "name": "decision_queues_retention_days_check", + "value": "\"decision_queues\".\"retention_days\" IS NULL OR (\"decision_queues\".\"retention_days\" >= 1 AND \"decision_queues\".\"retention_days\" <= 3650)" + } + }, + "isRLSEnabled": false + }, + "public.decision_retention": { + "name": "decision_retention", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_activity_at": { + "name": "source_activity_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "keep": { + "name": "keep", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "archived_reason": { + "name": "archived_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_by_type": { + "name": "archived_by_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_by_agent_id": { + "name": "archived_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "archived_by_user_id": { + "name": "archived_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_by_run_id": { + "name": "archived_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "archive_version": { + "name": "archive_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "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": { + "decision_retention_company_source_uq": { + "name": "decision_retention_company_source_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_retention_company_archived_idx": { + "name": "decision_retention_company_archived_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_retention_company_id_companies_id_fk": { + "name": "decision_retention_company_id_companies_id_fk", + "tableFrom": "decision_retention", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_retention_archived_by_agent_id_agents_id_fk": { + "name": "decision_retention_archived_by_agent_id_agents_id_fk", + "tableFrom": "decision_retention", + "tableTo": "agents", + "columnsFrom": [ + "archived_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_retention_archived_by_run_id_heartbeat_runs_id_fk": { + "name": "decision_retention_archived_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "decision_retention", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "archived_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "decision_retention_archive_actor_check": { + "name": "decision_retention_archive_actor_check", + "value": "(\n (\"decision_retention\".\"archived_at\" IS NULL AND \"decision_retention\".\"archived_by_type\" IS NULL AND \"decision_retention\".\"archived_by_agent_id\" IS NULL AND \"decision_retention\".\"archived_by_user_id\" IS NULL)\n OR (\"decision_retention\".\"archived_at\" IS NOT NULL AND \"decision_retention\".\"archived_by_type\" = 'system' AND \"decision_retention\".\"archived_by_agent_id\" IS NULL AND \"decision_retention\".\"archived_by_user_id\" IS NULL)\n OR (\"decision_retention\".\"archived_at\" IS NOT NULL AND \"decision_retention\".\"archived_by_type\" = 'agent' AND \"decision_retention\".\"archived_by_agent_id\" IS NOT NULL AND \"decision_retention\".\"archived_by_user_id\" IS NULL)\n OR (\"decision_retention\".\"archived_at\" IS NOT NULL AND \"decision_retention\".\"archived_by_type\" = 'user' AND \"decision_retention\".\"archived_by_agent_id\" IS NULL AND \"decision_retention\".\"archived_by_user_id\" IS NOT NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.decision_triage": { + "name": "decision_triage", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "decide_by": { + "name": "decide_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decide_by_date": { + "name": "decide_by_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "snoozed_until": { + "name": "snoozed_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "set_by_type": { + "name": "set_by_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "set_by_agent_id": { + "name": "set_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "set_by_user_id": { + "name": "set_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "set_by_run_id": { + "name": "set_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "set_by_agent_api_key_id": { + "name": "set_by_agent_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "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": { + "decision_triage_company_source_uq": { + "name": "decision_triage_company_source_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_triage_company_decide_by_idx": { + "name": "decision_triage_company_decide_by_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "decide_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_triage_company_id_companies_id_fk": { + "name": "decision_triage_company_id_companies_id_fk", + "tableFrom": "decision_triage", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_triage_set_by_agent_id_agents_id_fk": { + "name": "decision_triage_set_by_agent_id_agents_id_fk", + "tableFrom": "decision_triage", + "tableTo": "agents", + "columnsFrom": [ + "set_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_triage_set_by_run_id_heartbeat_runs_id_fk": { + "name": "decision_triage_set_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "decision_triage", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "set_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_triage_set_by_agent_api_key_id_agent_api_keys_id_fk": { + "name": "decision_triage_set_by_agent_api_key_id_agent_api_keys_id_fk", + "tableFrom": "decision_triage", + "tableTo": "agent_api_keys", + "columnsFrom": [ + "set_by_agent_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "decision_triage_actor_check": { + "name": "decision_triage_actor_check", + "value": "(\n (\"decision_triage\".\"set_by_type\" = 'agent' AND \"decision_triage\".\"set_by_agent_id\" IS NOT NULL AND \"decision_triage\".\"set_by_user_id\" IS NULL)\n OR (\"decision_triage\".\"set_by_type\" = 'user' AND \"decision_triage\".\"set_by_agent_id\" IS NULL AND \"decision_triage\".\"set_by_user_id\" IS NOT NULL)\n )" + }, + "decision_triage_decide_by_check": { + "name": "decision_triage_decide_by_check", + "value": "(\n (\"decision_triage\".\"decide_by\" IS NULL AND \"decision_triage\".\"decide_by_date\" IS NULL)\n OR (\"decision_triage\".\"decide_by\" IN ('today', 'this_week', 'whenever') AND \"decision_triage\".\"decide_by_date\" IS NULL)\n OR (\"decision_triage\".\"decide_by\" = 'date' AND \"decision_triage\".\"decide_by_date\" IS NOT NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.decision_triage_events": { + "name": "decision_triage_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_agent_id": { + "name": "actor_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_run_id": { + "name": "actor_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "agent_api_key_id": { + "name": "agent_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "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": { + "decision_triage_events_company_source_created_idx": { + "name": "decision_triage_events_company_source_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_triage_events_queue_created_idx": { + "name": "decision_triage_events_queue_created_idx", + "columns": [ + { + "expression": "queue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_triage_events_company_id_companies_id_fk": { + "name": "decision_triage_events_company_id_companies_id_fk", + "tableFrom": "decision_triage_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_triage_events_queue_id_decision_queues_id_fk": { + "name": "decision_triage_events_queue_id_decision_queues_id_fk", + "tableFrom": "decision_triage_events", + "tableTo": "decision_queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_triage_events_actor_agent_id_agents_id_fk": { + "name": "decision_triage_events_actor_agent_id_agents_id_fk", + "tableFrom": "decision_triage_events", + "tableTo": "agents", + "columnsFrom": [ + "actor_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_triage_events_actor_run_id_heartbeat_runs_id_fk": { + "name": "decision_triage_events_actor_run_id_heartbeat_runs_id_fk", + "tableFrom": "decision_triage_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "actor_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_triage_events_agent_api_key_id_agent_api_keys_id_fk": { + "name": "decision_triage_events_agent_api_key_id_agent_api_keys_id_fk", + "tableFrom": "decision_triage_events", + "tableTo": "agent_api_keys", + "columnsFrom": [ + "agent_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "decision_triage_events_actor_check": { + "name": "decision_triage_events_actor_check", + "value": "(\n (\"decision_triage_events\".\"actor_type\" = 'agent' AND \"decision_triage_events\".\"actor_agent_id\" IS NOT NULL AND \"decision_triage_events\".\"actor_user_id\" IS NULL)\n OR (\"decision_triage_events\".\"actor_type\" = 'user' AND \"decision_triage_events\".\"actor_agent_id\" IS NULL AND \"decision_triage_events\".\"actor_user_id\" IS NOT NULL)\n OR (\"decision_triage_events\".\"actor_type\" = 'system' AND \"decision_triage_events\".\"actor_agent_id\" IS NULL AND \"decision_triage_events\".\"actor_user_id\" IS NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.decision_training_examples": { + "name": "decision_training_examples", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "cutoff_at": { + "name": "cutoff_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "notes_history": { + "name": "notes_history", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "decision_outcome": { + "name": "decision_outcome", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "retention_policy": { + "name": "retention_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'scrub_deleted_comments_v1'" + }, + "snapshot": { + "name": "snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_training_examples_company_created_at_idx": { + "name": "decision_training_examples_company_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_training_examples_issue_idx": { + "name": "decision_training_examples_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_training_examples_source_author_uq": { + "name": "decision_training_examples_source_author_uq", + "columns": [ + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_training_examples_company_id_companies_id_fk": { + "name": "decision_training_examples_company_id_companies_id_fk", + "tableFrom": "decision_training_examples", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_training_examples_issue_id_issues_id_fk": { + "name": "decision_training_examples_issue_id_issues_id_fk", + "tableFrom": "decision_training_examples", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.decision_bundles": { + "name": "decision_bundles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_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 + }, + "origin_agent_id": { + "name": "origin_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "origin_issue_id": { + "name": "origin_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "origin_run_id": { + "name": "origin_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_bundles_company_created_at_idx": { + "name": "decision_bundles_company_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_bundles_company_id_companies_id_fk": { + "name": "decision_bundles_company_id_companies_id_fk", + "tableFrom": "decision_bundles", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_bundles_origin_agent_id_agents_id_fk": { + "name": "decision_bundles_origin_agent_id_agents_id_fk", + "tableFrom": "decision_bundles", + "tableTo": "agents", + "columnsFrom": [ + "origin_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_bundles_origin_issue_id_issues_id_fk": { + "name": "decision_bundles_origin_issue_id_issues_id_fk", + "tableFrom": "decision_bundles", + "tableTo": "issues", + "columnsFrom": [ + "origin_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_bundles_origin_run_id_heartbeat_runs_id_fk": { + "name": "decision_bundles_origin_run_id_heartbeat_runs_id_fk", + "tableFrom": "decision_bundles", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "origin_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.decision_effect_executions": { + "name": "decision_effect_executions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "decision_id": { + "name": "decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "effect_index": { + "name": "effect_index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "effect_type": { + "name": "effect_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_issue_id": { + "name": "target_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'claimed'" + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "activity_log_id": { + "name": "activity_log_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "executed_at": { + "name": "executed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "decision_effect_executions_decision_effect_uq": { + "name": "decision_effect_executions_decision_effect_uq", + "columns": [ + { + "expression": "decision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "effect_index", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_effect_executions_target_issue_idx": { + "name": "decision_effect_executions_target_issue_idx", + "columns": [ + { + "expression": "target_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_effect_executions_decision_id_decisions_id_fk": { + "name": "decision_effect_executions_decision_id_decisions_id_fk", + "tableFrom": "decision_effect_executions", + "tableTo": "decisions", + "columnsFrom": [ + "decision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_effect_executions_target_issue_id_issues_id_fk": { + "name": "decision_effect_executions_target_issue_id_issues_id_fk", + "tableFrom": "decision_effect_executions", + "tableTo": "issues", + "columnsFrom": [ + "target_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_effect_executions_activity_log_id_activity_log_id_fk": { + "name": "decision_effect_executions_activity_log_id_activity_log_id_fk", + "tableFrom": "decision_effect_executions", + "tableTo": "activity_log", + "columnsFrom": [ + "activity_log_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.decision_target_issues": { + "name": "decision_target_issues", + "schema": "", + "columns": { + "decision_id": { + "name": "decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "decision_target_issues_decision_idx": { + "name": "decision_target_issues_decision_idx", + "columns": [ + { + "expression": "decision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_target_issues_issue_idx": { + "name": "decision_target_issues_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_target_issues_decision_id_decisions_id_fk": { + "name": "decision_target_issues_decision_id_decisions_id_fk", + "tableFrom": "decision_target_issues", + "tableTo": "decisions", + "columnsFrom": [ + "decision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_target_issues_issue_id_issues_id_fk": { + "name": "decision_target_issues_issue_id_issues_id_fk", + "tableFrom": "decision_target_issues", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_target_issues_company_id_companies_id_fk": { + "name": "decision_target_issues_company_id_companies_id_fk", + "tableFrom": "decision_target_issues", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "decision_target_issues_decision_id_issue_id_pk": { + "name": "decision_target_issues_decision_id_issue_id_pk", + "columns": [ + "decision_id", + "issue_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.decisions": { + "name": "decisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "bundle_id": { + "name": "bundle_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "origin_agent_id": { + "name": "origin_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "origin_issue_id": { + "name": "origin_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "origin_run_id": { + "name": "origin_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "rule_key": { + "name": "rule_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "options": { + "name": "options", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "inputs": { + "name": "inputs", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "execution_status": { + "name": "execution_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "chosen_option_id": { + "name": "chosen_option_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "input_values": { + "name": "input_values", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "decided_by_user_id": { + "name": "decided_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_at": { + "name": "decided_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "signed_spec": { + "name": "signed_spec", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_snapshots": { + "name": "target_snapshots", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "continuation_policy": { + "name": "continuation_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "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()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decisions_company_status_expires_at_idx": { + "name": "decisions_company_status_expires_at_idx", + "columns": [ + { + "expression": "company_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": {} + }, + "decisions_bundle_idx": { + "name": "decisions_bundle_idx", + "columns": [ + { + "expression": "bundle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decisions_origin_issue_idx": { + "name": "decisions_origin_issue_idx", + "columns": [ + { + "expression": "origin_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decisions_company_idempotency_uq": { + "name": "decisions_company_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"decisions\".\"idempotency_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decisions_company_id_companies_id_fk": { + "name": "decisions_company_id_companies_id_fk", + "tableFrom": "decisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decisions_bundle_id_decision_bundles_id_fk": { + "name": "decisions_bundle_id_decision_bundles_id_fk", + "tableFrom": "decisions", + "tableTo": "decision_bundles", + "columnsFrom": [ + "bundle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "decisions_origin_agent_id_agents_id_fk": { + "name": "decisions_origin_agent_id_agents_id_fk", + "tableFrom": "decisions", + "tableTo": "agents", + "columnsFrom": [ + "origin_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decisions_origin_issue_id_issues_id_fk": { + "name": "decisions_origin_issue_id_issues_id_fk", + "tableFrom": "decisions", + "tableTo": "issues", + "columnsFrom": [ + "origin_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decisions_origin_run_id_heartbeat_runs_id_fk": { + "name": "decisions_origin_run_id_heartbeat_runs_id_fk", + "tableFrom": "decisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "origin_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.document_annotation_anchor_snapshots": { + "name": "document_annotation_anchor_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "thread_id": { + "name": "thread_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "from_revision_id": { + "name": "from_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "from_revision_number": { + "name": "from_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "to_revision_id": { + "name": "to_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "to_revision_number": { + "name": "to_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "previous_anchor": { + "name": "previous_anchor", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "next_anchor": { + "name": "next_anchor", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "anchor_state": { + "name": "anchor_state", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "anchor_confidence": { + "name": "anchor_confidence", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "document_annotation_anchor_snapshots_company_thread_created_at_idx": { + "name": "document_annotation_anchor_snapshots_company_thread_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "thread_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_anchor_snapshots_company_document_revision_idx": { + "name": "document_annotation_anchor_snapshots_company_document_revision_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "to_revision_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_annotation_anchor_snapshots_company_id_companies_id_fk": { + "name": "document_annotation_anchor_snapshots_company_id_companies_id_fk", + "tableFrom": "document_annotation_anchor_snapshots", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "document_annotation_anchor_snapshots_thread_id_document_annotation_threads_id_fk": { + "name": "document_annotation_anchor_snapshots_thread_id_document_annotation_threads_id_fk", + "tableFrom": "document_annotation_anchor_snapshots", + "tableTo": "document_annotation_threads", + "columnsFrom": [ + "thread_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_anchor_snapshots_document_id_documents_id_fk": { + "name": "document_annotation_anchor_snapshots_document_id_documents_id_fk", + "tableFrom": "document_annotation_anchor_snapshots", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_anchor_snapshots_from_revision_id_document_revisions_id_fk": { + "name": "document_annotation_anchor_snapshots_from_revision_id_document_revisions_id_fk", + "tableFrom": "document_annotation_anchor_snapshots", + "tableTo": "document_revisions", + "columnsFrom": [ + "from_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_annotation_anchor_snapshots_to_revision_id_document_revisions_id_fk": { + "name": "document_annotation_anchor_snapshots_to_revision_id_document_revisions_id_fk", + "tableFrom": "document_annotation_anchor_snapshots", + "tableTo": "document_revisions", + "columnsFrom": [ + "to_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.document_annotation_comments": { + "name": "document_annotation_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "thread_id": { + "name": "thread_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_type": { + "name": "author_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_comment_id": { + "name": "issue_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_trust": { + "name": "source_trust", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "document_annotation_comments_company_thread_created_at_idx": { + "name": "document_annotation_comments_company_thread_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "thread_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_comments_company_issue_created_at_idx": { + "name": "document_annotation_comments_company_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_comments_company_routine_created_at_idx": { + "name": "document_annotation_comments_company_routine_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_comments_company_case_created_at_idx": { + "name": "document_annotation_comments_company_case_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_comments_company_document_created_at_idx": { + "name": "document_annotation_comments_company_document_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_comments_issue_comment_idx": { + "name": "document_annotation_comments_issue_comment_idx", + "columns": [ + { + "expression": "issue_comment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_comments_body_search_idx": { + "name": "document_annotation_comments_body_search_idx", + "columns": [ + { + "expression": "body", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "document_annotation_comments_company_id_companies_id_fk": { + "name": "document_annotation_comments_company_id_companies_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "document_annotation_comments_thread_id_document_annotation_threads_id_fk": { + "name": "document_annotation_comments_thread_id_document_annotation_threads_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "document_annotation_threads", + "columnsFrom": [ + "thread_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_comments_issue_id_issues_id_fk": { + "name": "document_annotation_comments_issue_id_issues_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_comments_routine_id_routines_id_fk": { + "name": "document_annotation_comments_routine_id_routines_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_comments_case_id_cases_id_fk": { + "name": "document_annotation_comments_case_id_cases_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_comments_document_id_documents_id_fk": { + "name": "document_annotation_comments_document_id_documents_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_comments_author_agent_id_agents_id_fk": { + "name": "document_annotation_comments_author_agent_id_agents_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_annotation_comments_created_by_run_id_heartbeat_runs_id_fk": { + "name": "document_annotation_comments_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_annotation_comments_issue_comment_id_issue_comments_id_fk": { + "name": "document_annotation_comments_issue_comment_id_issue_comments_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "issue_comments", + "columnsFrom": [ + "issue_comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "document_annotation_comments_exactly_one_owner_chk": { + "name": "document_annotation_comments_exactly_one_owner_chk", + "value": "num_nonnulls(\"document_annotation_comments\".\"issue_id\", \"document_annotation_comments\".\"routine_id\", \"document_annotation_comments\".\"case_id\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.document_annotation_threads": { + "name": "document_annotation_threads", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_key": { + "name": "document_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "anchor_state": { + "name": "anchor_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "original_revision_id": { + "name": "original_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "original_revision_number": { + "name": "original_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "current_revision_id": { + "name": "current_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "current_revision_number": { + "name": "current_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "selected_text": { + "name": "selected_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prefix_text": { + "name": "prefix_text", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "suffix_text": { + "name": "suffix_text", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "normalized_start": { + "name": "normalized_start", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "normalized_end": { + "name": "normalized_end", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "markdown_start": { + "name": "markdown_start", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "markdown_end": { + "name": "markdown_end", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "anchor_confidence": { + "name": "anchor_confidence", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'exact'" + }, + "anchor_selector": { + "name": "anchor_selector", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_by_agent_id": { + "name": "resolved_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_by_user_id": { + "name": "resolved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "document_annotation_threads_company_document_status_idx": { + "name": "document_annotation_threads_company_document_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_threads_company_issue_status_idx": { + "name": "document_annotation_threads_company_issue_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_threads_company_routine_status_idx": { + "name": "document_annotation_threads_company_routine_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_threads_company_case_status_idx": { + "name": "document_annotation_threads_company_case_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_threads_company_current_revision_open_idx": { + "name": "document_annotation_threads_company_current_revision_open_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "current_revision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_threads_company_anchor_state_idx": { + "name": "document_annotation_threads_company_anchor_state_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "anchor_state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_annotation_threads_company_id_companies_id_fk": { + "name": "document_annotation_threads_company_id_companies_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "document_annotation_threads_issue_id_issues_id_fk": { + "name": "document_annotation_threads_issue_id_issues_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_threads_routine_id_routines_id_fk": { + "name": "document_annotation_threads_routine_id_routines_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_threads_case_id_cases_id_fk": { + "name": "document_annotation_threads_case_id_cases_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_threads_document_id_documents_id_fk": { + "name": "document_annotation_threads_document_id_documents_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_threads_original_revision_id_document_revisions_id_fk": { + "name": "document_annotation_threads_original_revision_id_document_revisions_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "document_revisions", + "columnsFrom": [ + "original_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_annotation_threads_current_revision_id_document_revisions_id_fk": { + "name": "document_annotation_threads_current_revision_id_document_revisions_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "document_revisions", + "columnsFrom": [ + "current_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_annotation_threads_created_by_agent_id_agents_id_fk": { + "name": "document_annotation_threads_created_by_agent_id_agents_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_annotation_threads_resolved_by_agent_id_agents_id_fk": { + "name": "document_annotation_threads_resolved_by_agent_id_agents_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "agents", + "columnsFrom": [ + "resolved_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "document_annotation_threads_exactly_one_owner_chk": { + "name": "document_annotation_threads_exactly_one_owner_chk", + "value": "num_nonnulls(\"document_annotation_threads\".\"issue_id\", \"document_annotation_threads\".\"routine_id\", \"document_annotation_threads\".\"case_id\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.document_memberships": { + "name": "document_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "starred_at": { + "name": "starred_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": { + "document_memberships_company_user_starred_idx": { + "name": "document_memberships_company_user_starred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "starred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_memberships_company_user_document_uq": { + "name": "document_memberships_company_user_document_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_memberships_company_id_companies_id_fk": { + "name": "document_memberships_company_id_companies_id_fk", + "tableFrom": "document_memberships", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_memberships_document_id_documents_id_fk": { + "name": "document_memberships_document_id_documents_id_fk", + "tableFrom": "document_memberships", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.document_revisions": { + "name": "document_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision_number": { + "name": "revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown'" + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "change_summary": { + "name": "change_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "document_revisions_document_revision_uq": { + "name": "document_revisions_document_revision_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_revisions_company_document_created_idx": { + "name": "document_revisions_company_document_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_revisions_company_id_companies_id_fk": { + "name": "document_revisions_company_id_companies_id_fk", + "tableFrom": "document_revisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_revisions_document_id_documents_id_fk": { + "name": "document_revisions_document_id_documents_id_fk", + "tableFrom": "document_revisions", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_revisions_created_by_agent_id_agents_id_fk": { + "name": "document_revisions_created_by_agent_id_agents_id_fk", + "tableFrom": "document_revisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_revisions_created_by_run_id_heartbeat_runs_id_fk": { + "name": "document_revisions_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "document_revisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.documents": { + "name": "documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown'" + }, + "latest_body": { + "name": "latest_body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "latest_revision_id": { + "name": "latest_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "latest_revision_number": { + "name": "latest_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "locked_at": { + "name": "locked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "locked_by_agent_id": { + "name": "locked_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "locked_by_user_id": { + "name": "locked_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_trust": { + "name": "source_trust", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "documents_company_updated_idx": { + "name": "documents_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "documents_company_created_idx": { + "name": "documents_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "documents_title_search_idx": { + "name": "documents_title_search_idx", + "columns": [ + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "documents_latest_body_search_idx": { + "name": "documents_latest_body_search_idx", + "columns": [ + { + "expression": "latest_body", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "documents_company_id_companies_id_fk": { + "name": "documents_company_id_companies_id_fk", + "tableFrom": "documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "documents_created_by_agent_id_agents_id_fk": { + "name": "documents_created_by_agent_id_agents_id_fk", + "tableFrom": "documents", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "documents_updated_by_agent_id_agents_id_fk": { + "name": "documents_updated_by_agent_id_agents_id_fk", + "tableFrom": "documents", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "documents_locked_by_agent_id_agents_id_fk": { + "name": "documents_locked_by_agent_id_agents_id_fk", + "tableFrom": "documents", + "tableTo": "agents", + "columnsFrom": [ + "locked_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.email_endpoints": { + "name": "email_endpoints", + "schema": "", + "columns": { + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "receive_mode": { + "name": "receive_mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "webhook_id": { + "name": "webhook_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owned_api_key_id": { + "name": "owned_api_key_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "activation_at": { + "name": "activation_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "sync_checkpoint": { + "name": "sync_checkpoint", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "email_endpoints_company_id_endpoint_id_chat_endpoints_company_id_id_fk": { + "name": "email_endpoints_company_id_endpoint_id_chat_endpoints_company_id_id_fk", + "tableFrom": "email_endpoints", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "email_endpoints_receive_mode_check": { + "name": "email_endpoints_receive_mode_check", + "value": "\"email_endpoints\".\"receive_mode\" in ('websocket', 'webhook')" + } + }, + "isRLSEnabled": false + }, + "public.email_messages": { + "name": "email_messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider_message_id": { + "name": "provider_message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "envelope": { + "name": "envelope", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "full_text": { + "name": "full_text", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "automatic": { + "name": "automatic", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "attachment_ids": { + "name": "attachment_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "email_messages_provider_uq": { + "name": "email_messages_provider_uq", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "email_messages_conversation_idx": { + "name": "email_messages_conversation_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "timestamp", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "email_messages_company_id_endpoint_id_chat_endpoints_company_id_id_fk": { + "name": "email_messages_company_id_endpoint_id_chat_endpoints_company_id_id_fk", + "tableFrom": "email_messages", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "email_messages_company_id_conversation_id_chat_conversations_company_id_id_fk": { + "name": "email_messages_company_id_conversation_id_chat_conversations_company_id_id_fk", + "tableFrom": "email_messages", + "tableTo": "chat_conversations", + "columnsFrom": [ + "company_id", + "conversation_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "email_messages_direction_check": { + "name": "email_messages_direction_check", + "value": "\"email_messages\".\"direction\" in ('inbound', 'outbound')" + } + }, + "isRLSEnabled": false + }, + "public.email_sends": { + "name": "email_sends", + "schema": "", + "columns": { + "publication_id": { + "name": "publication_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "request": { + "name": "request", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "actor": { + "name": "actor", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "digest": { + "name": "digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "first_attempt_at": { + "name": "first_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "email_sends_pending_idx": { + "name": "email_sends_pending_idx", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "outcome", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"email_sends\".\"outcome\" in ('queued', 'uncertain')", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "email_sends_company_id_endpoint_id_chat_endpoints_company_id_id_fk": { + "name": "email_sends_company_id_endpoint_id_chat_endpoints_company_id_id_fk", + "tableFrom": "email_sends", + "tableTo": "chat_endpoints", + "columnsFrom": [ + "company_id", + "endpoint_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "email_sends_company_id_publication_id_chat_publications_company_id_id_fk": { + "name": "email_sends_company_id_publication_id_chat_publications_company_id_id_fk", + "tableFrom": "email_sends", + "tableTo": "chat_publications", + "columnsFrom": [ + "company_id", + "publication_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "email_sends_outcome_check": { + "name": "email_sends_outcome_check", + "value": "\"email_sends\".\"outcome\" in ('queued', 'sent', 'delivered', 'failed', 'uncertain')" + } + }, + "isRLSEnabled": false + }, + "public.environment_custom_image_setup_sessions": { + "name": "environment_custom_image_setup_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "environment_id": { + "name": "environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "template_id": { + "name": "template_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "promoted_template_id": { + "name": "promoted_template_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_lease_id": { + "name": "provider_lease_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "environment_lease_id": { + "name": "environment_lease_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'starting'" + }, + "started_by_user_id": { + "name": "started_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_by_agent_id": { + "name": "started_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "base_template_ref": { + "name": "base_template_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "connection_summary": { + "name": "connection_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "connection_secret_ref": { + "name": "connection_secret_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "environment_custom_image_setup_sessions_environment_status_idx": { + "name": "environment_custom_image_setup_sessions_environment_status_idx", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_setup_sessions_environment_active_uq": { + "name": "environment_custom_image_setup_sessions_environment_active_uq", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"environment_custom_image_setup_sessions\".\"status\" IN ('starting', 'waiting_for_user', 'capturing')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_setup_sessions_template_idx": { + "name": "environment_custom_image_setup_sessions_template_idx", + "columns": [ + { + "expression": "template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_setup_sessions_promoted_template_idx": { + "name": "environment_custom_image_setup_sessions_promoted_template_idx", + "columns": [ + { + "expression": "promoted_template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_setup_sessions_expires_idx": { + "name": "environment_custom_image_setup_sessions_expires_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_setup_sessions_provider_lease_idx": { + "name": "environment_custom_image_setup_sessions_provider_lease_idx", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_lease_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "environment_custom_image_setup_sessions_environment_id_environments_id_fk": { + "name": "environment_custom_image_setup_sessions_environment_id_environments_id_fk", + "tableFrom": "environment_custom_image_setup_sessions", + "tableTo": "environments", + "columnsFrom": [ + "environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "environment_custom_image_setup_sessions_template_id_environment_custom_image_templates_id_fk": { + "name": "environment_custom_image_setup_sessions_template_id_environment_custom_image_templates_id_fk", + "tableFrom": "environment_custom_image_setup_sessions", + "tableTo": "environment_custom_image_templates", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_custom_image_setup_sessions_promoted_template_id_environment_custom_image_templates_id_fk": { + "name": "environment_custom_image_setup_sessions_promoted_template_id_environment_custom_image_templates_id_fk", + "tableFrom": "environment_custom_image_setup_sessions", + "tableTo": "environment_custom_image_templates", + "columnsFrom": [ + "promoted_template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_custom_image_setup_sessions_environment_lease_id_environment_leases_id_fk": { + "name": "environment_custom_image_setup_sessions_environment_lease_id_environment_leases_id_fk", + "tableFrom": "environment_custom_image_setup_sessions", + "tableTo": "environment_leases", + "columnsFrom": [ + "environment_lease_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_custom_image_setup_sessions_started_by_agent_id_agents_id_fk": { + "name": "environment_custom_image_setup_sessions_started_by_agent_id_agents_id_fk", + "tableFrom": "environment_custom_image_setup_sessions", + "tableTo": "agents", + "columnsFrom": [ + "started_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.environment_custom_image_templates": { + "name": "environment_custom_image_templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "environment_id": { + "name": "environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "template_kind": { + "name": "template_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "template_ref": { + "name": "template_ref", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_template_ref": { + "name": "source_template_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_environment_config_fingerprint": { + "name": "source_environment_config_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "captured_at": { + "name": "captured_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "superseded_by_template_id": { + "name": "superseded_by_template_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "environment_custom_image_templates_environment_status_idx": { + "name": "environment_custom_image_templates_environment_status_idx", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_templates_environment_provider_status_idx": { + "name": "environment_custom_image_templates_environment_provider_status_idx", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_templates_environment_active_uq": { + "name": "environment_custom_image_templates_environment_active_uq", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"environment_custom_image_templates\".\"status\" = 'active'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_templates_superseded_by_idx": { + "name": "environment_custom_image_templates_superseded_by_idx", + "columns": [ + { + "expression": "superseded_by_template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_templates_last_used_idx": { + "name": "environment_custom_image_templates_last_used_idx", + "columns": [ + { + "expression": "last_used_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "environment_custom_image_templates_environment_id_environments_id_fk": { + "name": "environment_custom_image_templates_environment_id_environments_id_fk", + "tableFrom": "environment_custom_image_templates", + "tableTo": "environments", + "columnsFrom": [ + "environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "environment_custom_image_templates_created_by_agent_id_agents_id_fk": { + "name": "environment_custom_image_templates_created_by_agent_id_agents_id_fk", + "tableFrom": "environment_custom_image_templates", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_custom_image_templates_superseded_by_template_id_environment_custom_image_templates_id_fk": { + "name": "environment_custom_image_templates_superseded_by_template_id_environment_custom_image_templates_id_fk", + "tableFrom": "environment_custom_image_templates", + "tableTo": "environment_custom_image_templates", + "columnsFrom": [ + "superseded_by_template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.environment_leases": { + "name": "environment_leases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "environment_id": { + "name": "environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "lease_policy": { + "name": "lease_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'ephemeral'" + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_lease_id": { + "name": "provider_lease_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "acquired_at": { + "name": "acquired_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": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "released_at": { + "name": "released_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cleanup_status": { + "name": "cleanup_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "environment_leases_company_environment_status_idx": { + "name": "environment_leases_company_environment_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_leases_company_execution_workspace_idx": { + "name": "environment_leases_company_execution_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_leases_company_issue_idx": { + "name": "environment_leases_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_leases_heartbeat_run_idx": { + "name": "environment_leases_heartbeat_run_idx", + "columns": [ + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_leases_company_last_used_idx": { + "name": "environment_leases_company_last_used_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_used_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_leases_provider_lease_idx": { + "name": "environment_leases_provider_lease_idx", + "columns": [ + { + "expression": "provider_lease_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "environment_leases_company_id_companies_id_fk": { + "name": "environment_leases_company_id_companies_id_fk", + "tableFrom": "environment_leases", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "environment_leases_environment_id_environments_id_fk": { + "name": "environment_leases_environment_id_environments_id_fk", + "tableFrom": "environment_leases", + "tableTo": "environments", + "columnsFrom": [ + "environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_leases_execution_workspace_id_execution_workspaces_id_fk": { + "name": "environment_leases_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "environment_leases", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_leases_issue_id_issues_id_fk": { + "name": "environment_leases_issue_id_issues_id_fk", + "tableFrom": "environment_leases", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_leases_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "environment_leases_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "environment_leases", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.environments": { + "name": "environments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "driver": { + "name": "driver", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "env_vars": { + "name": "env_vars", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "environments_status_idx": { + "name": "environments_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environments_local_driver_idx": { + "name": "environments_local_driver_idx", + "columns": [ + { + "expression": "driver", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"environments\".\"driver\" = 'local'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "environments_managed_sandbox_idx": { + "name": "environments_managed_sandbox_idx", + "columns": [ + { + "expression": "driver", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"environments\".\"driver\" = 'sandbox' AND (\"environments\".\"metadata\" ->> 'managedByPaperclip')::boolean = true", + "concurrently": false, + "method": "btree", + "with": {} + }, + "environments_name_idx": { + "name": "environments_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_workspace_runtime_leases": { + "name": "execution_workspace_runtime_leases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "owner_key": { + "name": "owner_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_issue_id": { + "name": "owner_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_run_id": { + "name": "owner_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_action": { + "name": "last_action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "renewed_at": { + "name": "renewed_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 + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_workspace_runtime_leases_company_workspace_idx": { + "name": "execution_workspace_runtime_leases_company_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspace_runtime_leases_company_owner_idx": { + "name": "execution_workspace_runtime_leases_company_owner_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspace_runtime_leases_expires_at_idx": { + "name": "execution_workspace_runtime_leases_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_workspace_runtime_leases_company_id_companies_id_fk": { + "name": "execution_workspace_runtime_leases_company_id_companies_id_fk", + "tableFrom": "execution_workspace_runtime_leases", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_workspace_runtime_leases_execution_workspace_id_execution_workspaces_id_fk": { + "name": "execution_workspace_runtime_leases_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "execution_workspace_runtime_leases", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_workspace_runtime_leases_owner_issue_id_issues_id_fk": { + "name": "execution_workspace_runtime_leases_owner_issue_id_issues_id_fk", + "tableFrom": "execution_workspace_runtime_leases", + "tableTo": "issues", + "columnsFrom": [ + "owner_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspace_runtime_leases_owner_run_id_heartbeat_runs_id_fk": { + "name": "execution_workspace_runtime_leases_owner_run_id_heartbeat_runs_id_fk", + "tableFrom": "execution_workspace_runtime_leases", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "owner_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspace_runtime_leases_owner_agent_id_agents_id_fk": { + "name": "execution_workspace_runtime_leases_owner_agent_id_agents_id_fk", + "tableFrom": "execution_workspace_runtime_leases", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "execution_workspace_runtime_leases_execution_workspace_id_unique": { + "name": "execution_workspace_runtime_leases_execution_workspace_id_unique", + "nullsNotDistinct": false, + "columns": [ + "execution_workspace_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_workspaces": { + "name": "execution_workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "strategy_type": { + "name": "strategy_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_url": { + "name": "repo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "base_ref": { + "name": "base_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "branch_name": { + "name": "branch_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_type": { + "name": "provider_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_fs'" + }, + "provider_ref": { + "name": "provider_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "derived_from_execution_workspace_id": { + "name": "derived_from_execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "opened_at": { + "name": "opened_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 + }, + "cleanup_eligible_at": { + "name": "cleanup_eligible_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cleanup_reason": { + "name": "cleanup_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_workspaces_company_project_status_idx": { + "name": "execution_workspaces_company_project_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_project_workspace_status_idx": { + "name": "execution_workspaces_company_project_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_source_issue_idx": { + "name": "execution_workspaces_company_source_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_last_used_idx": { + "name": "execution_workspaces_company_last_used_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_used_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_branch_idx": { + "name": "execution_workspaces_company_branch_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "branch_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_workspaces_company_id_companies_id_fk": { + "name": "execution_workspaces_company_id_companies_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_workspaces_project_id_projects_id_fk": { + "name": "execution_workspaces_project_id_projects_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_workspaces_project_workspace_id_project_workspaces_id_fk": { + "name": "execution_workspaces_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspaces_source_issue_id_issues_id_fk": { + "name": "execution_workspaces_source_issue_id_issues_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspaces_derived_from_execution_workspace_id_execution_workspaces_id_fk": { + "name": "execution_workspaces_derived_from_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "derived_from_execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.external_object_mentions": { + "name": "external_object_mentions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_record_id": { + "name": "source_record_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "document_key": { + "name": "document_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "property_key": { + "name": "property_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "matched_text_redacted": { + "name": "matched_text_redacted", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sanitized_display_url": { + "name": "sanitized_display_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "canonical_identity_hash": { + "name": "canonical_identity_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "canonical_identity": { + "name": "canonical_identity", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "object_id": { + "name": "object_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider_key": { + "name": "provider_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "detector_key": { + "name": "detector_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "object_type": { + "name": "object_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "confidence": { + "name": "confidence", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'exact'" + }, + "created_by_plugin_id": { + "name": "created_by_plugin_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": { + "external_object_mentions_company_source_issue_idx": { + "name": "external_object_mentions_company_source_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_object_mentions_company_object_idx": { + "name": "external_object_mentions_company_object_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_object_mentions_company_provider_idx": { + "name": "external_object_mentions_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_object_mentions_company_source_record_uq": { + "name": "external_object_mentions_company_source_record_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_record_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "property_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "canonical_identity_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"external_object_mentions\".\"source_record_id\" is not null and \"external_object_mentions\".\"canonical_identity_hash\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_object_mentions_company_source_null_record_uq": { + "name": "external_object_mentions_company_source_null_record_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "property_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "canonical_identity_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"external_object_mentions\".\"source_record_id\" is null and \"external_object_mentions\".\"canonical_identity_hash\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "external_object_mentions_company_id_companies_id_fk": { + "name": "external_object_mentions_company_id_companies_id_fk", + "tableFrom": "external_object_mentions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "external_object_mentions_source_issue_id_issues_id_fk": { + "name": "external_object_mentions_source_issue_id_issues_id_fk", + "tableFrom": "external_object_mentions", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "external_object_mentions_object_id_external_objects_id_fk": { + "name": "external_object_mentions_object_id_external_objects_id_fk", + "tableFrom": "external_object_mentions", + "tableTo": "external_objects", + "columnsFrom": [ + "object_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "external_object_mentions_created_by_plugin_id_plugins_id_fk": { + "name": "external_object_mentions_created_by_plugin_id_plugins_id_fk", + "tableFrom": "external_object_mentions", + "tableTo": "plugins", + "columnsFrom": [ + "created_by_plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.external_objects": { + "name": "external_objects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider_key": { + "name": "provider_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "object_type": { + "name": "object_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sanitized_canonical_url": { + "name": "sanitized_canonical_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "canonical_identity_hash": { + "name": "canonical_identity_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "display_key": { + "name": "display_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon_key": { + "name": "icon_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "display_title": { + "name": "display_title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_key": { + "name": "status_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_label": { + "name": "status_label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_icon_key": { + "name": "status_icon_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_category": { + "name": "status_category", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "status_tone": { + "name": "status_tone", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'neutral'" + }, + "liveness": { + "name": "liveness", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "is_terminal": { + "name": "is_terminal", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "remote_version": { + "name": "remote_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "etag": { + "name": "etag", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_resolved_at": { + "name": "last_resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_changed_at": { + "name": "last_changed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error_at": { + "name": "last_error_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_refresh_at": { + "name": "next_refresh_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_started_at": { + "name": "refresh_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_error_code": { + "name": "last_error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error_message": { + "name": "last_error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "external_objects_company_provider_object_idx": { + "name": "external_objects_company_provider_object_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_objects_company_provider_status_idx": { + "name": "external_objects_company_provider_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status_category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_objects_company_refresh_idx": { + "name": "external_objects_company_refresh_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_refresh_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_objects_company_external_id_uq": { + "name": "external_objects_company_external_id_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_objects_company_identity_uq": { + "name": "external_objects_company_identity_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "canonical_identity_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "external_objects_company_id_companies_id_fk": { + "name": "external_objects_company_id_companies_id_fk", + "tableFrom": "external_objects", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "external_objects_plugin_id_plugins_id_fk": { + "name": "external_objects_plugin_id_plugins_id_fk", + "tableFrom": "external_objects", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.feedback_exports": { + "name": "feedback_exports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "feedback_vote_id": { + "name": "feedback_vote_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vote": { + "name": "vote", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_only'" + }, + "destination": { + "name": "destination", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "export_id": { + "name": "export_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "consent_version": { + "name": "consent_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "schema_version": { + "name": "schema_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip-feedback-envelope-v2'" + }, + "bundle_version": { + "name": "bundle_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip-feedback-bundle-v2'" + }, + "payload_version": { + "name": "payload_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip-feedback-v1'" + }, + "payload_digest": { + "name": "payload_digest", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload_snapshot": { + "name": "payload_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "target_summary": { + "name": "target_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "redaction_summary": { + "name": "redaction_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempted_at": { + "name": "last_attempted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "exported_at": { + "name": "exported_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "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": { + "feedback_exports_feedback_vote_idx": { + "name": "feedback_exports_feedback_vote_idx", + "columns": [ + { + "expression": "feedback_vote_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_created_idx": { + "name": "feedback_exports_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_status_idx": { + "name": "feedback_exports_company_status_idx", + "columns": [ + { + "expression": "company_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": {} + }, + "feedback_exports_company_issue_idx": { + "name": "feedback_exports_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_project_idx": { + "name": "feedback_exports_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_author_idx": { + "name": "feedback_exports_company_author_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "feedback_exports_company_id_companies_id_fk": { + "name": "feedback_exports_company_id_companies_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "feedback_exports_feedback_vote_id_feedback_votes_id_fk": { + "name": "feedback_exports_feedback_vote_id_feedback_votes_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "feedback_votes", + "columnsFrom": [ + "feedback_vote_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "feedback_exports_issue_id_issues_id_fk": { + "name": "feedback_exports_issue_id_issues_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "feedback_exports_project_id_projects_id_fk": { + "name": "feedback_exports_project_id_projects_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.feedback_votes": { + "name": "feedback_votes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "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 + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vote": { + "name": "vote", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shared_with_labs": { + "name": "shared_with_labs", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "shared_at": { + "name": "shared_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "consent_version": { + "name": "consent_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redaction_summary": { + "name": "redaction_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "feedback_votes_company_issue_idx": { + "name": "feedback_votes_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_votes_issue_target_idx": { + "name": "feedback_votes_issue_target_idx", + "columns": [ + { + "expression": "issue_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": {} + }, + "feedback_votes_author_idx": { + "name": "feedback_votes_author_idx", + "columns": [ + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_votes_company_target_author_idx": { + "name": "feedback_votes_company_target_author_idx", + "columns": [ + { + "expression": "company_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" + }, + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "feedback_votes_company_id_companies_id_fk": { + "name": "feedback_votes_company_id_companies_id_fk", + "tableFrom": "feedback_votes", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "feedback_votes_issue_id_issues_id_fk": { + "name": "feedback_votes_issue_id_issues_id_fk", + "tableFrom": "feedback_votes", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.finance_events": { + "name": "finance_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cost_event_id": { + "name": "cost_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "event_kind": { + "name": "event_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'debit'" + }, + "biller": { + "name": "biller", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_adapter_type": { + "name": "execution_adapter_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pricing_tier": { + "name": "pricing_tier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "amount_cents": { + "name": "amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "estimated": { + "name": "estimated", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "external_invoice_id": { + "name": "external_invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata_json": { + "name": "metadata_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "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": { + "finance_events_company_occurred_idx": { + "name": "finance_events_company_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_biller_occurred_idx": { + "name": "finance_events_company_biller_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "biller", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_kind_occurred_idx": { + "name": "finance_events_company_kind_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "event_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_direction_occurred_idx": { + "name": "finance_events_company_direction_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "direction", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_heartbeat_run_idx": { + "name": "finance_events_company_heartbeat_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_cost_event_idx": { + "name": "finance_events_company_cost_event_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "finance_events_company_id_companies_id_fk": { + "name": "finance_events_company_id_companies_id_fk", + "tableFrom": "finance_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_agent_id_agents_id_fk": { + "name": "finance_events_agent_id_agents_id_fk", + "tableFrom": "finance_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_issue_id_issues_id_fk": { + "name": "finance_events_issue_id_issues_id_fk", + "tableFrom": "finance_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "finance_events_project_id_projects_id_fk": { + "name": "finance_events_project_id_projects_id_fk", + "tableFrom": "finance_events", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_goal_id_goals_id_fk": { + "name": "finance_events_goal_id_goals_id_fk", + "tableFrom": "finance_events", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "finance_events_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "finance_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_cost_event_id_cost_events_id_fk": { + "name": "finance_events_cost_event_id_cost_events_id_fk", + "tableFrom": "finance_events", + "tableTo": "cost_events", + "columnsFrom": [ + "cost_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.folders": { + "name": "folders", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "system_key": { + "name": "system_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "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": { + "folders_company_kind_position_idx": { + "name": "folders_company_kind_position_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "folders_company_kind_root_slug_uq": { + "name": "folders_company_kind_root_slug_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"folders\".\"parent_id\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "folders_company_kind_parent_slug_uq": { + "name": "folders_company_kind_parent_slug_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"folders\".\"parent_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "folders_company_kind_system_key_uq": { + "name": "folders_company_kind_system_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "system_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"folders\".\"system_key\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "folders_company_kind_parent_position_idx": { + "name": "folders_company_kind_parent_position_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "folders_company_id_companies_id_fk": { + "name": "folders_company_id_companies_id_fk", + "tableFrom": "folders", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "folders_parent_id_folders_id_fk": { + "name": "folders_parent_id_folders_id_fk", + "tableFrom": "folders", + "tableTo": "folders", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.goals": { + "name": "goals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'task'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'planned'" + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_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": { + "goals_company_idx": { + "name": "goals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "goals_company_id_companies_id_fk": { + "name": "goals_company_id_companies_id_fk", + "tableFrom": "goals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "goals_parent_id_goals_id_fk": { + "name": "goals_parent_id_goals_id_fk", + "tableFrom": "goals", + "tableTo": "goals", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "goals_owner_agent_id_agents_id_fk": { + "name": "goals_owner_agent_id_agents_id_fk", + "tableFrom": "goals", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.heartbeat_run_events": { + "name": "heartbeat_run_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "seq": { + "name": "seq", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stream": { + "name": "stream", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "source_instance_id": { + "name": "source_instance_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_event_id": { + "name": "source_event_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_seq": { + "name": "source_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "source_payload_sha256": { + "name": "source_payload_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "protocol_schema_version": { + "name": "protocol_schema_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "heartbeat_run_events_run_seq_uq": { + "name": "heartbeat_run_events_run_seq_uq", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "seq", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_run_source_event_uq": { + "name": "heartbeat_run_events_run_source_event_uq", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"heartbeat_run_events\".\"source_event_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_run_source_seq_uq": { + "name": "heartbeat_run_events_run_source_seq_uq", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_seq", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"heartbeat_run_events\".\"source_instance_id\" is not null and \"heartbeat_run_events\".\"source_seq\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_company_run_idx": { + "name": "heartbeat_run_events_company_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_company_created_idx": { + "name": "heartbeat_run_events_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "heartbeat_run_events_company_id_companies_id_fk": { + "name": "heartbeat_run_events_company_id_companies_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_run_events_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_run_events_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_run_events_agent_id_agents_id_fk": { + "name": "heartbeat_run_events_agent_id_agents_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.heartbeat_run_watchdog_decisions": { + "name": "heartbeat_run_watchdog_decisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "evaluation_issue_id": { + "name": "evaluation_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "decision": { + "name": "decision", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "snoozed_until": { + "name": "snoozed_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "heartbeat_run_watchdog_decisions_company_run_created_idx": { + "name": "heartbeat_run_watchdog_decisions_company_run_created_idx", + "columns": [ + { + "expression": "company_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": {} + }, + "heartbeat_run_watchdog_decisions_company_run_snooze_idx": { + "name": "heartbeat_run_watchdog_decisions_company_run_snooze_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "snoozed_until", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "heartbeat_run_watchdog_decisions_company_id_companies_id_fk": { + "name": "heartbeat_run_watchdog_decisions_company_id_companies_id_fk", + "tableFrom": "heartbeat_run_watchdog_decisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_run_watchdog_decisions_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_run_watchdog_decisions_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_run_watchdog_decisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "heartbeat_run_watchdog_decisions_evaluation_issue_id_issues_id_fk": { + "name": "heartbeat_run_watchdog_decisions_evaluation_issue_id_issues_id_fk", + "tableFrom": "heartbeat_run_watchdog_decisions", + "tableTo": "issues", + "columnsFrom": [ + "evaluation_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "heartbeat_run_watchdog_decisions_created_by_agent_id_agents_id_fk": { + "name": "heartbeat_run_watchdog_decisions_created_by_agent_id_agents_id_fk", + "tableFrom": "heartbeat_run_watchdog_decisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "heartbeat_run_watchdog_decisions_created_by_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_run_watchdog_decisions_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_run_watchdog_decisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.heartbeat_runs": { + "name": "heartbeat_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "invocation_source": { + "name": "invocation_source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'on_demand'" + }, + "trigger_detail": { + "name": "trigger_detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "active_identity_context_id": { + "name": "active_identity_context_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "execution_control_deadline_at": { + "name": "execution_control_deadline_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "execution_status_delivery_id": { + "name": "execution_status_delivery_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "wakeup_request_id": { + "name": "wakeup_request_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "exit_code": { + "name": "exit_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "signal": { + "name": "signal", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "usage_json": { + "name": "usage_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "result_json": { + "name": "result_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "runtime_mode": { + "name": "runtime_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'legacy'" + }, + "runtime_mode_resolver_version": { + "name": "runtime_mode_resolver_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "runtime_mode_reason": { + "name": "runtime_mode_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "runtime_mode_resolved_at": { + "name": "runtime_mode_resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "runner_profile_json": { + "name": "runner_profile_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "runner_instance_id": { + "name": "runner_instance_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "native_session_id": { + "name": "native_session_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "native_issue_id": { + "name": "native_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "driver_kind": { + "name": "driver_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "driver_version": { + "name": "driver_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completion_contract_id": { + "name": "completion_contract_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "completion_contract_sha256": { + "name": "completion_contract_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_event_seq": { + "name": "next_event_seq", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "native_phase": { + "name": "native_phase", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "native_phase_updated_at": { + "name": "native_phase_updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "session_id_before": { + "name": "session_id_before", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_id_after": { + "name": "session_id_after", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_store": { + "name": "log_store", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_ref": { + "name": "log_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_bytes": { + "name": "log_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "log_sha256": { + "name": "log_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_compressed": { + "name": "log_compressed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "stdout_excerpt": { + "name": "stdout_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stderr_excerpt": { + "name": "stderr_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_run_id": { + "name": "external_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "controller_boot_id": { + "name": "controller_boot_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "controller_lease_expires_at": { + "name": "controller_lease_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "execution_stage": { + "name": "execution_stage", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "process_pid": { + "name": "process_pid", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "process_group_id": { + "name": "process_group_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "process_started_at": { + "name": "process_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_output_at": { + "name": "last_output_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_output_seq": { + "name": "last_output_seq", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_output_stream": { + "name": "last_output_stream", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_output_bytes": { + "name": "last_output_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "retry_of_run_id": { + "name": "retry_of_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "process_loss_retry_count": { + "name": "process_loss_retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "scheduled_retry_at": { + "name": "scheduled_retry_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scheduled_retry_attempt": { + "name": "scheduled_retry_attempt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "scheduled_retry_reason": { + "name": "scheduled_retry_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_comment_status": { + "name": "issue_comment_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'not_applicable'" + }, + "issue_comment_satisfied_by_comment_id": { + "name": "issue_comment_satisfied_by_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_comment_retry_queued_at": { + "name": "issue_comment_retry_queued_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "liveness_state": { + "name": "liveness_state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "liveness_reason": { + "name": "liveness_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "continuation_attempt": { + "name": "continuation_attempt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_useful_action_at": { + "name": "last_useful_action_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_action": { + "name": "next_action", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "context_snapshot": { + "name": "context_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "heartbeat_runs_execution_status_delivery_idx": { + "name": "heartbeat_runs_execution_status_delivery_idx", + "columns": [ + { + "expression": "execution_status_delivery_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"heartbeat_runs\".\"execution_status_delivery_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_execution_control_deadline_idx": { + "name": "heartbeat_runs_execution_control_deadline_idx", + "columns": [ + { + "expression": "execution_control_deadline_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"heartbeat_runs\".\"execution_control_deadline_at\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_native_replacement_predecessor_uq": { + "name": "heartbeat_runs_native_replacement_predecessor_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "retry_of_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"heartbeat_runs\".\"scheduled_retry_reason\" = 'native_safe_replacement'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_agent_started_idx": { + "name": "heartbeat_runs_company_agent_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_responsible_user_idx": { + "name": "heartbeat_runs_company_responsible_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "responsible_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_liveness_idx": { + "name": "heartbeat_runs_company_liveness_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "liveness_state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_status_last_output_idx": { + "name": "heartbeat_runs_company_status_last_output_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_output_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_status_process_started_idx": { + "name": "heartbeat_runs_company_status_process_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "process_started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_created_at_desc_idx": { + "name": "heartbeat_runs_company_created_at_desc_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_ctx_issue_created_idx": { + "name": "heartbeat_runs_company_ctx_issue_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "(\"context_snapshot\" ->> 'issueId')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_ctx_task_created_idx": { + "name": "heartbeat_runs_company_ctx_task_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "(\"context_snapshot\" ->> 'taskId')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_ctx_taskkey_created_idx": { + "name": "heartbeat_runs_company_ctx_taskkey_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "(\"context_snapshot\" ->> 'taskKey')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "heartbeat_runs_company_id_companies_id_fk": { + "name": "heartbeat_runs_company_id_companies_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_agent_id_agents_id_fk": { + "name": "heartbeat_runs_agent_id_agents_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_wakeup_request_id_agent_wakeup_requests_id_fk": { + "name": "heartbeat_runs_wakeup_request_id_agent_wakeup_requests_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "agent_wakeup_requests", + "columnsFrom": [ + "wakeup_request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_retry_of_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_runs_retry_of_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "retry_of_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "heartbeat_runs_company_native_issue_id_uq": { + "name": "heartbeat_runs_company_native_issue_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "native_issue_id", + "id" + ] + }, + "heartbeat_runs_company_native_issue_contract_id_uq": { + "name": "heartbeat_runs_company_native_issue_contract_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "native_issue_id", + "id", + "completion_contract_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.inbox_dismissals": { + "name": "inbox_dismissals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "item_key": { + "name": "item_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'dismiss'" + }, + "dismissed_at": { + "name": "dismissed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "snoozed_until": { + "name": "snoozed_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "inbox_dismissals_company_user_idx": { + "name": "inbox_dismissals_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_dismissals_company_item_idx": { + "name": "inbox_dismissals_company_item_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "item_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_dismissals_company_user_item_idx": { + "name": "inbox_dismissals_company_user_item_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "item_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "inbox_dismissals_company_id_companies_id_fk": { + "name": "inbox_dismissals_company_id_companies_id_fk", + "tableFrom": "inbox_dismissals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.connection_grant_delegations": { + "name": "connection_grant_delegations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "connection_grant_delegations_company_agent_idx": { + "name": "connection_grant_delegations_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_grant_delegations_grant_agent_uq": { + "name": "connection_grant_delegations_grant_agent_uq", + "columns": [ + { + "expression": "grant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "connection_grant_delegations_company_id_companies_id_fk": { + "name": "connection_grant_delegations_company_id_companies_id_fk", + "tableFrom": "connection_grant_delegations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_grant_delegations_agent_id_agents_id_fk": { + "name": "connection_grant_delegations_agent_id_agents_id_fk", + "tableFrom": "connection_grant_delegations", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_grant_delegations_company_grant_fk": { + "name": "connection_grant_delegations_company_grant_fk", + "tableFrom": "connection_grant_delegations", + "tableTo": "connection_grants", + "columnsFrom": [ + "company_id", + "grant_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.connection_grant_members": { + "name": "connection_grant_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "subject_type": { + "name": "subject_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_id": { + "name": "subject_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "connection_grant_members_company_subject_idx": { + "name": "connection_grant_members_company_subject_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_grant_members_grant_subject_uq": { + "name": "connection_grant_members_grant_subject_uq", + "columns": [ + { + "expression": "grant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "connection_grant_members_company_id_companies_id_fk": { + "name": "connection_grant_members_company_id_companies_id_fk", + "tableFrom": "connection_grant_members", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_grant_members_company_grant_fk": { + "name": "connection_grant_members_company_grant_fk", + "tableFrom": "connection_grant_members", + "tableTo": "connection_grants", + "columnsFrom": [ + "company_id", + "grant_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "connection_grant_members_subject_type_check": { + "name": "connection_grant_members_subject_type_check", + "value": "\"connection_grant_members\".\"subject_type\" in ('user')" + } + }, + "isRLSEnabled": false + }, + "public.connection_grants": { + "name": "connection_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_user_id": { + "name": "subject_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subject_agent_id": { + "name": "subject_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider_tenant": { + "name": "provider_tenant", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "credential_secret_refs": { + "name": "credential_secret_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "external_credential": { + "name": "external_credential", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_by_agent_id": { + "name": "revoked_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "revoked_by_user_id": { + "name": "revoked_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_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": { + "connection_grants_company_connection_idx": { + "name": "connection_grants_company_connection_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_grants_subject_user_idx": { + "name": "connection_grants_subject_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_grants_subject_agent_idx": { + "name": "connection_grants_subject_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_grants_user_uq": { + "name": "connection_grants_user_uq", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_grants_agent_uq": { + "name": "connection_grants_agent_uq", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_grants_default_uq": { + "name": "connection_grants_default_uq", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"connection_grants\".\"is_default\" = true and \"connection_grants\".\"kind\" = 'organization'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "connection_grants_company_id_companies_id_fk": { + "name": "connection_grants_company_id_companies_id_fk", + "tableFrom": "connection_grants", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_grants_subject_agent_id_agents_id_fk": { + "name": "connection_grants_subject_agent_id_agents_id_fk", + "tableFrom": "connection_grants", + "tableTo": "agents", + "columnsFrom": [ + "subject_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_grants_created_by_agent_id_agents_id_fk": { + "name": "connection_grants_created_by_agent_id_agents_id_fk", + "tableFrom": "connection_grants", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "connection_grants_revoked_by_agent_id_agents_id_fk": { + "name": "connection_grants_revoked_by_agent_id_agents_id_fk", + "tableFrom": "connection_grants", + "tableTo": "agents", + "columnsFrom": [ + "revoked_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "connection_grants_company_connection_fk": { + "name": "connection_grants_company_connection_fk", + "tableFrom": "connection_grants", + "tableTo": "tool_connections", + "columnsFrom": [ + "company_id", + "connection_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "connection_grants_company_id_uq": { + "name": "connection_grants_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "connection_grants_kind_check": { + "name": "connection_grants_kind_check", + "value": "\"connection_grants\".\"kind\" in ('organization', 'user', 'agent')" + }, + "connection_grants_status_check": { + "name": "connection_grants_status_check", + "value": "\"connection_grants\".\"status\" in ('active', 'revoked', 'expired', 'needs_reauthorization')" + }, + "connection_grants_credential_source_one_of_check": { + "name": "connection_grants_credential_source_one_of_check", + "value": "\"connection_grants\".\"external_credential\" is null or jsonb_array_length(\"connection_grants\".\"credential_secret_refs\") = 0" + }, + "connection_grants_subject_check": { + "name": "connection_grants_subject_check", + "value": "(\"connection_grants\".\"kind\" = 'user' and \"connection_grants\".\"subject_user_id\" is not null and \"connection_grants\".\"subject_agent_id\" is null) or (\"connection_grants\".\"kind\" = 'agent' and \"connection_grants\".\"subject_agent_id\" is not null and \"connection_grants\".\"subject_user_id\" is null) or (\"connection_grants\".\"kind\" = 'organization' and \"connection_grants\".\"subject_user_id\" is null and \"connection_grants\".\"subject_agent_id\" is null)" + }, + "connection_grants_default_check": { + "name": "connection_grants_default_check", + "value": "\"connection_grants\".\"is_default\" = false or \"connection_grants\".\"kind\" = 'organization'" + } + }, + "isRLSEnabled": false + }, + "public.connection_token_issuances": { + "name": "connection_token_issuances", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requested_scope": { + "name": "requested_scope", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "issued_scope": { + "name": "issued_scope", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "ttl_seconds": { + "name": "ttl_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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": { + "connection_token_issuances_company_created_idx": { + "name": "connection_token_issuances_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_token_issuances_connection_created_idx": { + "name": "connection_token_issuances_connection_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_token_issuances_agent_connection_idx": { + "name": "connection_token_issuances_agent_connection_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_token_issuances_run_idx": { + "name": "connection_token_issuances_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "connection_token_issuances_company_id_companies_id_fk": { + "name": "connection_token_issuances_company_id_companies_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_token_issuances_application_id_tool_applications_id_fk": { + "name": "connection_token_issuances_application_id_tool_applications_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "connection_token_issuances_connection_id_tool_connections_id_fk": { + "name": "connection_token_issuances_connection_id_tool_connections_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_token_issuances_agent_id_agents_id_fk": { + "name": "connection_token_issuances_agent_id_agents_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_token_issuances_run_id_heartbeat_runs_id_fk": { + "name": "connection_token_issuances_run_id_heartbeat_runs_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "connection_token_issuances_issue_id_issues_id_fk": { + "name": "connection_token_issuances_issue_id_issues_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "connection_token_issuances_project_id_projects_id_fk": { + "name": "connection_token_issuances_project_id_projects_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.instance_settings": { + "name": "instance_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "singleton_key": { + "name": "singleton_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "default_environment_id": { + "name": "default_environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "general": { + "name": "general", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "experimental": { + "name": "experimental", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "instance_settings_singleton_key_idx": { + "name": "instance_settings_singleton_key_idx", + "columns": [ + { + "expression": "singleton_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "instance_settings_default_environment_id_environments_id_fk": { + "name": "instance_settings_default_environment_id_environments_id_fk", + "tableFrom": "instance_settings", + "tableTo": "environments", + "columnsFrom": [ + "default_environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.instance_user_roles": { + "name": "instance_user_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'instance_admin'" + }, + "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": { + "instance_user_roles_user_role_unique_idx": { + "name": "instance_user_roles_user_role_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "instance_user_roles_role_idx": { + "name": "instance_user_roles_role_idx", + "columns": [ + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invites": { + "name": "invites", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "invite_type": { + "name": "invite_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company_join'" + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "allowed_join_types": { + "name": "allowed_join_types", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'both'" + }, + "defaults_payload": { + "name": "defaults_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "invited_by_user_id": { + "name": "invited_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "invites_token_hash_unique_idx": { + "name": "invites_token_hash_unique_idx", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invites_company_invite_state_idx": { + "name": "invites_company_invite_state_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "invite_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revoked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invites_company_id_companies_id_fk": { + "name": "invites_company_id_companies_id_fk", + "tableFrom": "invites", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_approvals": { + "name": "issue_approvals", + "schema": "", + "columns": { + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "linked_by_agent_id": { + "name": "linked_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "linked_by_user_id": { + "name": "linked_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_approvals_issue_idx": { + "name": "issue_approvals_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_approvals_approval_idx": { + "name": "issue_approvals_approval_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_approvals_company_idx": { + "name": "issue_approvals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_approvals_company_id_companies_id_fk": { + "name": "issue_approvals_company_id_companies_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_approvals_issue_id_issues_id_fk": { + "name": "issue_approvals_issue_id_issues_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_approvals_approval_id_approvals_id_fk": { + "name": "issue_approvals_approval_id_approvals_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_approvals_linked_by_agent_id_agents_id_fk": { + "name": "issue_approvals_linked_by_agent_id_agents_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "agents", + "columnsFrom": [ + "linked_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "issue_approvals_pk": { + "name": "issue_approvals_pk", + "columns": [ + "issue_id", + "approval_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_attachments": { + "name": "issue_attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_comment_id": { + "name": "issue_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "originating_run_id": { + "name": "originating_run_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": { + "issue_attachments_company_issue_idx": { + "name": "issue_attachments_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_attachments_issue_comment_idx": { + "name": "issue_attachments_issue_comment_idx", + "columns": [ + { + "expression": "issue_comment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_attachments_originating_run_idx": { + "name": "issue_attachments_originating_run_idx", + "columns": [ + { + "expression": "originating_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_attachments_asset_uq": { + "name": "issue_attachments_asset_uq", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_attachments_company_id_companies_id_fk": { + "name": "issue_attachments_company_id_companies_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_attachments_issue_id_issues_id_fk": { + "name": "issue_attachments_issue_id_issues_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_attachments_asset_id_assets_id_fk": { + "name": "issue_attachments_asset_id_assets_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_attachments_issue_comment_id_issue_comments_id_fk": { + "name": "issue_attachments_issue_comment_id_issue_comments_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "issue_comments", + "columnsFrom": [ + "issue_comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_attachments_originating_run_id_heartbeat_runs_id_fk": { + "name": "issue_attachments_originating_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "originating_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_comments": { + "name": "issue_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "on_behalf_of_user_id": { + "name": "on_behalf_of_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "author_type": { + "name": "author_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "derived_author_agent_id": { + "name": "derived_author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "derived_created_by_run_id": { + "name": "derived_created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "derived_author_source": { + "name": "derived_author_source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_request_id": { + "name": "client_request_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "conversation_session_generation": { + "name": "conversation_session_generation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "presentation": { + "name": "presentation", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "deleted_by_type": { + "name": "deleted_by_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_by_agent_id": { + "name": "deleted_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "deleted_by_user_id": { + "name": "deleted_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_by_run_id": { + "name": "deleted_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_trust": { + "name": "source_trust", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_comments_issue_idx": { + "name": "issue_comments_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_idx": { + "name": "issue_comments_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_issue_created_at_idx": { + "name": "issue_comments_company_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_author_issue_created_at_idx": { + "name": "issue_comments_company_author_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_body_search_idx": { + "name": "issue_comments_body_search_idx", + "columns": [ + { + "expression": "body", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "issue_comments_company_id_companies_id_fk": { + "name": "issue_comments_company_id_companies_id_fk", + "tableFrom": "issue_comments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_comments_issue_id_issues_id_fk": { + "name": "issue_comments_issue_id_issues_id_fk", + "tableFrom": "issue_comments", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_comments_author_agent_id_agents_id_fk": { + "name": "issue_comments_author_agent_id_agents_id_fk", + "tableFrom": "issue_comments", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_comments_on_behalf_of_user_id_user_id_fk": { + "name": "issue_comments_on_behalf_of_user_id_user_id_fk", + "tableFrom": "issue_comments", + "tableTo": "user", + "columnsFrom": [ + "on_behalf_of_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_comments_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_comments_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_comments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_comments_derived_author_agent_id_agents_id_fk": { + "name": "issue_comments_derived_author_agent_id_agents_id_fk", + "tableFrom": "issue_comments", + "tableTo": "agents", + "columnsFrom": [ + "derived_author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_comments_derived_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_comments_derived_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_comments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "derived_created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_comments_deleted_by_agent_id_agents_id_fk": { + "name": "issue_comments_deleted_by_agent_id_agents_id_fk", + "tableFrom": "issue_comments", + "tableTo": "agents", + "columnsFrom": [ + "deleted_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_comments_deleted_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_comments_deleted_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_comments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "deleted_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "issue_comments_client_request_uq": { + "name": "issue_comments_client_request_uq", + "nullsNotDistinct": false, + "columns": [ + "issue_id", + "author_user_id", + "client_request_id" + ] + }, + "issue_comments_company_id_uq": { + "name": "issue_comments_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_create_idempotency_keys": { + "name": "issue_create_idempotency_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_create_idempotency_keys_company_key_uq": { + "name": "issue_create_idempotency_keys_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_create_idempotency_keys_issue_idx": { + "name": "issue_create_idempotency_keys_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_create_idempotency_keys_company_created_at_idx": { + "name": "issue_create_idempotency_keys_company_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_create_idempotency_keys_company_id_companies_id_fk": { + "name": "issue_create_idempotency_keys_company_id_companies_id_fk", + "tableFrom": "issue_create_idempotency_keys", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_create_idempotency_keys_issue_id_issues_id_fk": { + "name": "issue_create_idempotency_keys_issue_id_issues_id_fk", + "tableFrom": "issue_create_idempotency_keys", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_documents": { + "name": "issue_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "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": { + "issue_documents_company_issue_key_uq": { + "name": "issue_documents_company_issue_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_documents_document_uq": { + "name": "issue_documents_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_documents_company_issue_updated_idx": { + "name": "issue_documents_company_issue_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_documents_company_id_companies_id_fk": { + "name": "issue_documents_company_id_companies_id_fk", + "tableFrom": "issue_documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_documents_issue_id_issues_id_fk": { + "name": "issue_documents_issue_id_issues_id_fk", + "tableFrom": "issue_documents", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_documents_document_id_documents_id_fk": { + "name": "issue_documents_document_id_documents_id_fk", + "tableFrom": "issue_documents", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_execution_decisions": { + "name": "issue_execution_decisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "stage_id": { + "name": "stage_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "stage_type": { + "name": "stage_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_agent_id": { + "name": "actor_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_run_id": { + "name": "created_by_run_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": { + "issue_execution_decisions_company_issue_idx": { + "name": "issue_execution_decisions_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_execution_decisions_stage_idx": { + "name": "issue_execution_decisions_stage_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_execution_decisions_company_id_companies_id_fk": { + "name": "issue_execution_decisions_company_id_companies_id_fk", + "tableFrom": "issue_execution_decisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_execution_decisions_issue_id_issues_id_fk": { + "name": "issue_execution_decisions_issue_id_issues_id_fk", + "tableFrom": "issue_execution_decisions", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_execution_decisions_actor_agent_id_agents_id_fk": { + "name": "issue_execution_decisions_actor_agent_id_agents_id_fk", + "tableFrom": "issue_execution_decisions", + "tableTo": "agents", + "columnsFrom": [ + "actor_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_execution_decisions_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_execution_decisions_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_execution_decisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_inbox_archives": { + "name": "issue_inbox_archives", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "archived_by_actor_type": { + "name": "archived_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'user'" + }, + "archived_by_agent_id": { + "name": "archived_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "archived_by_run_id": { + "name": "archived_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_inbox_archives_company_issue_idx": { + "name": "issue_inbox_archives_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_inbox_archives_company_user_idx": { + "name": "issue_inbox_archives_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_inbox_archives_company_issue_user_idx": { + "name": "issue_inbox_archives_company_issue_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_inbox_archives_company_id_companies_id_fk": { + "name": "issue_inbox_archives_company_id_companies_id_fk", + "tableFrom": "issue_inbox_archives", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_inbox_archives_issue_id_issues_id_fk": { + "name": "issue_inbox_archives_issue_id_issues_id_fk", + "tableFrom": "issue_inbox_archives", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_inbox_archives_archived_by_agent_id_agents_id_fk": { + "name": "issue_inbox_archives_archived_by_agent_id_agents_id_fk", + "tableFrom": "issue_inbox_archives", + "tableTo": "agents", + "columnsFrom": [ + "archived_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_inbox_archives_archived_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_inbox_archives_archived_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_inbox_archives", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "archived_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "issue_inbox_archives_archived_by_actor_type_check": { + "name": "issue_inbox_archives_archived_by_actor_type_check", + "value": "\"issue_inbox_archives\".\"archived_by_actor_type\" in ('user', 'agent')" + } + }, + "isRLSEnabled": false + }, + "public.issue_labels": { + "name": "issue_labels", + "schema": "", + "columns": { + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label_id": { + "name": "label_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_labels_issue_idx": { + "name": "issue_labels_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_labels_label_idx": { + "name": "issue_labels_label_idx", + "columns": [ + { + "expression": "label_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_labels_company_idx": { + "name": "issue_labels_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_labels_issue_id_issues_id_fk": { + "name": "issue_labels_issue_id_issues_id_fk", + "tableFrom": "issue_labels", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_labels_label_id_labels_id_fk": { + "name": "issue_labels_label_id_labels_id_fk", + "tableFrom": "issue_labels", + "tableTo": "labels", + "columnsFrom": [ + "label_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_labels_company_id_companies_id_fk": { + "name": "issue_labels_company_id_companies_id_fk", + "tableFrom": "issue_labels", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "issue_labels_pk": { + "name": "issue_labels_pk", + "columns": [ + "issue_id", + "label_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_plan_decompositions": { + "name": "issue_plan_decompositions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "accepted_plan_revision_id": { + "name": "accepted_plan_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "accepted_interaction_id": { + "name": "accepted_interaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'in_flight'" + }, + "request_fingerprint": { + "name": "request_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requested_child_count": { + "name": "requested_child_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "requested_children": { + "name": "requested_children", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "child_issue_ids": { + "name": "child_issue_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_run_id": { + "name": "owner_run_id", + "type": "uuid", + "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": { + "issue_plan_decompositions_company_source_status_idx": { + "name": "issue_plan_decompositions_company_source_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_plan_decompositions_active_owner_idx": { + "name": "issue_plan_decompositions_active_owner_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"issue_plan_decompositions\".\"status\" = 'in_flight'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_plan_decompositions_source_revision_uq": { + "name": "issue_plan_decompositions_source_revision_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "accepted_plan_revision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_plan_decompositions_company_id_companies_id_fk": { + "name": "issue_plan_decompositions_company_id_companies_id_fk", + "tableFrom": "issue_plan_decompositions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_plan_decompositions_source_issue_id_issues_id_fk": { + "name": "issue_plan_decompositions_source_issue_id_issues_id_fk", + "tableFrom": "issue_plan_decompositions", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_plan_decompositions_accepted_plan_revision_id_document_revisions_id_fk": { + "name": "issue_plan_decompositions_accepted_plan_revision_id_document_revisions_id_fk", + "tableFrom": "issue_plan_decompositions", + "tableTo": "document_revisions", + "columnsFrom": [ + "accepted_plan_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_plan_decompositions_accepted_interaction_id_issue_thread_interactions_id_fk": { + "name": "issue_plan_decompositions_accepted_interaction_id_issue_thread_interactions_id_fk", + "tableFrom": "issue_plan_decompositions", + "tableTo": "issue_thread_interactions", + "columnsFrom": [ + "accepted_interaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_plan_decompositions_owner_agent_id_agents_id_fk": { + "name": "issue_plan_decompositions_owner_agent_id_agents_id_fk", + "tableFrom": "issue_plan_decompositions", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_plan_decompositions_owner_run_id_heartbeat_runs_id_fk": { + "name": "issue_plan_decompositions_owner_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_plan_decompositions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "owner_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_question_response_deliveries": { + "name": "issue_question_response_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "interaction_id": { + "name": "interaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_run_id": { + "name": "source_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_run_id": { + "name": "target_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_turn_id": { + "name": "target_turn_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload_sha256": { + "name": "payload_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "delivery_mode": { + "name": "delivery_mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error_count": { + "name": "error_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "acknowledged_at": { + "name": "acknowledged_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error_code": { + "name": "last_error_code", + "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": { + "issue_question_response_deliveries_interaction_uq": { + "name": "issue_question_response_deliveries_interaction_uq", + "columns": [ + { + "expression": "interaction_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_question_response_deliveries_correlation_uq": { + "name": "issue_question_response_deliveries_correlation_uq", + "columns": [ + { + "expression": "correlation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_question_response_deliveries_pending_idx": { + "name": "issue_question_response_deliveries_pending_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_question_response_deliveries_company_issue_idx": { + "name": "issue_question_response_deliveries_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_question_response_deliveries_company_id_companies_id_fk": { + "name": "issue_question_response_deliveries_company_id_companies_id_fk", + "tableFrom": "issue_question_response_deliveries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_question_response_deliveries_issue_id_issues_id_fk": { + "name": "issue_question_response_deliveries_issue_id_issues_id_fk", + "tableFrom": "issue_question_response_deliveries", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_question_response_deliveries_interaction_id_issue_thread_interactions_id_fk": { + "name": "issue_question_response_deliveries_interaction_id_issue_thread_interactions_id_fk", + "tableFrom": "issue_question_response_deliveries", + "tableTo": "issue_thread_interactions", + "columnsFrom": [ + "interaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_question_response_deliveries_source_run_id_heartbeat_runs_id_fk": { + "name": "issue_question_response_deliveries_source_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_question_response_deliveries", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "source_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_question_response_deliveries_target_run_id_heartbeat_runs_id_fk": { + "name": "issue_question_response_deliveries_target_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_question_response_deliveries", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "target_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "issue_question_response_deliveries_status_check": { + "name": "issue_question_response_deliveries_status_check", + "value": "\"issue_question_response_deliveries\".\"status\" IN ('pending', 'delivering', 'delivered', 'fallback_queued', 'failed')" + }, + "issue_question_response_deliveries_mode_check": { + "name": "issue_question_response_deliveries_mode_check", + "value": "\"issue_question_response_deliveries\".\"delivery_mode\" IS NULL OR \"issue_question_response_deliveries\".\"delivery_mode\" IN ('steered', 'coalesced', 'wake_fallback')" + } + }, + "isRLSEnabled": false + }, + "public.issue_read_states": { + "name": "issue_read_states", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_read_at": { + "name": "last_read_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_read_states_company_issue_idx": { + "name": "issue_read_states_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_read_states_company_user_idx": { + "name": "issue_read_states_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_read_states_company_issue_user_idx": { + "name": "issue_read_states_company_issue_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_read_states_company_id_companies_id_fk": { + "name": "issue_read_states_company_id_companies_id_fk", + "tableFrom": "issue_read_states", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_read_states_issue_id_issues_id_fk": { + "name": "issue_read_states_issue_id_issues_id_fk", + "tableFrom": "issue_read_states", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_recovery_actions": { + "name": "issue_recovery_actions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "recovery_issue_id": { + "name": "recovery_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "owner_type": { + "name": "owner_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'agent'" + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previous_owner_agent_id": { + "name": "previous_owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "return_owner_agent_id": { + "name": "return_owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cause": { + "name": "cause", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fingerprint": { + "name": "fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "evidence": { + "name": "evidence", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "next_action": { + "name": "next_action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "wake_policy": { + "name": "wake_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "monitor_policy": { + "name": "monitor_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "max_attempts": { + "name": "max_attempts", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "timeout_at": { + "name": "timeout_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolution_note": { + "name": "resolution_note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_recovery_actions_company_source_status_idx": { + "name": "issue_recovery_actions_company_source_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_recovery_actions_company_owner_status_idx": { + "name": "issue_recovery_actions_company_owner_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_recovery_actions_company_recovery_issue_idx": { + "name": "issue_recovery_actions_company_recovery_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "recovery_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_recovery_actions_active_source_uq": { + "name": "issue_recovery_actions_active_source_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issue_recovery_actions\".\"status\" in ('active', 'escalated')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_recovery_actions_active_fingerprint_uq": { + "name": "issue_recovery_actions_active_fingerprint_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cause", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issue_recovery_actions\".\"status\" in ('active', 'escalated')", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_recovery_actions_company_id_companies_id_fk": { + "name": "issue_recovery_actions_company_id_companies_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_recovery_actions_source_issue_id_issues_id_fk": { + "name": "issue_recovery_actions_source_issue_id_issues_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_recovery_actions_recovery_issue_id_issues_id_fk": { + "name": "issue_recovery_actions_recovery_issue_id_issues_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "issues", + "columnsFrom": [ + "recovery_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_recovery_actions_owner_agent_id_agents_id_fk": { + "name": "issue_recovery_actions_owner_agent_id_agents_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_recovery_actions_previous_owner_agent_id_agents_id_fk": { + "name": "issue_recovery_actions_previous_owner_agent_id_agents_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "agents", + "columnsFrom": [ + "previous_owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_recovery_actions_return_owner_agent_id_agents_id_fk": { + "name": "issue_recovery_actions_return_owner_agent_id_agents_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "agents", + "columnsFrom": [ + "return_owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_reference_mentions": { + "name": "issue_reference_mentions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_issue_id": { + "name": "target_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_record_id": { + "name": "source_record_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "document_key": { + "name": "document_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "matched_text": { + "name": "matched_text", + "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": { + "issue_reference_mentions_company_source_issue_idx": { + "name": "issue_reference_mentions_company_source_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_reference_mentions_company_target_issue_idx": { + "name": "issue_reference_mentions_company_target_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_reference_mentions_company_issue_pair_idx": { + "name": "issue_reference_mentions_company_issue_pair_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_reference_mentions_company_source_mention_record_uq": { + "name": "issue_reference_mentions_company_source_mention_record_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_record_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issue_reference_mentions\".\"source_record_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_reference_mentions_company_source_mention_null_record_uq": { + "name": "issue_reference_mentions_company_source_mention_null_record_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issue_reference_mentions\".\"source_record_id\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_reference_mentions_company_id_companies_id_fk": { + "name": "issue_reference_mentions_company_id_companies_id_fk", + "tableFrom": "issue_reference_mentions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_reference_mentions_source_issue_id_issues_id_fk": { + "name": "issue_reference_mentions_source_issue_id_issues_id_fk", + "tableFrom": "issue_reference_mentions", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_reference_mentions_target_issue_id_issues_id_fk": { + "name": "issue_reference_mentions_target_issue_id_issues_id_fk", + "tableFrom": "issue_reference_mentions", + "tableTo": "issues", + "columnsFrom": [ + "target_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_relations": { + "name": "issue_relations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "related_issue_id": { + "name": "related_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_relations_company_issue_idx": { + "name": "issue_relations_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_relations_company_related_issue_idx": { + "name": "issue_relations_company_related_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "related_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_relations_company_type_idx": { + "name": "issue_relations_company_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_relations_company_edge_uq": { + "name": "issue_relations_company_edge_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "related_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_relations_company_id_companies_id_fk": { + "name": "issue_relations_company_id_companies_id_fk", + "tableFrom": "issue_relations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_relations_issue_id_issues_id_fk": { + "name": "issue_relations_issue_id_issues_id_fk", + "tableFrom": "issue_relations", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_relations_related_issue_id_issues_id_fk": { + "name": "issue_relations_related_issue_id_issues_id_fk", + "tableFrom": "issue_relations", + "tableTo": "issues", + "columnsFrom": [ + "related_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_relations_created_by_agent_id_agents_id_fk": { + "name": "issue_relations_created_by_agent_id_agents_id_fk", + "tableFrom": "issue_relations", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_thread_interactions": { + "name": "issue_thread_interactions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "continuation_policy": { + "name": "continuation_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'wake_assignee'" + }, + "requested_resolver_policy": { + "name": "requested_resolver_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'anyone'" + }, + "effective_resolver_policy": { + "name": "effective_resolver_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'anyone'" + }, + "resolver_policy_provenance": { + "name": "resolver_policy_provenance", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'inherited'" + }, + "effective_resolver_policy_source": { + "name": "effective_resolver_policy_source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'requested'" + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_comment_ids": { + "name": "origin_comment_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "source_comment_id": { + "name": "source_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_identity_context_id": { + "name": "source_identity_context_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_run_id": { + "name": "source_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "addressee_agent_id": { + "name": "addressee_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "addressee_user_id": { + "name": "addressee_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_by_agent_id": { + "name": "resolved_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_by_run_id": { + "name": "resolved_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_by_user_id": { + "name": "resolved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_thread_interactions_issue_idx": { + "name": "issue_thread_interactions_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_thread_interactions_company_issue_created_at_idx": { + "name": "issue_thread_interactions_company_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_thread_interactions_company_issue_status_idx": { + "name": "issue_thread_interactions_company_issue_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_thread_interactions_company_issue_idempotency_uq": { + "name": "issue_thread_interactions_company_issue_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issue_thread_interactions\".\"idempotency_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_thread_interactions_source_comment_idx": { + "name": "issue_thread_interactions_source_comment_idx", + "columns": [ + { + "expression": "source_comment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_thread_interactions_addressee_agent_idx": { + "name": "issue_thread_interactions_addressee_agent_idx", + "columns": [ + { + "expression": "addressee_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_thread_interactions_addressee_user_idx": { + "name": "issue_thread_interactions_addressee_user_idx", + "columns": [ + { + "expression": "addressee_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_thread_interactions_company_id_companies_id_fk": { + "name": "issue_thread_interactions_company_id_companies_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_thread_interactions_issue_id_issues_id_fk": { + "name": "issue_thread_interactions_issue_id_issues_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_thread_interactions_source_comment_id_issue_comments_id_fk": { + "name": "issue_thread_interactions_source_comment_id_issue_comments_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "issue_comments", + "columnsFrom": [ + "source_comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_thread_interactions_source_run_id_heartbeat_runs_id_fk": { + "name": "issue_thread_interactions_source_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "source_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_thread_interactions_created_by_agent_id_agents_id_fk": { + "name": "issue_thread_interactions_created_by_agent_id_agents_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_thread_interactions_addressee_agent_id_agents_id_fk": { + "name": "issue_thread_interactions_addressee_agent_id_agents_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "agents", + "columnsFrom": [ + "addressee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_thread_interactions_resolved_by_agent_id_agents_id_fk": { + "name": "issue_thread_interactions_resolved_by_agent_id_agents_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "agents", + "columnsFrom": [ + "resolved_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_thread_interactions_resolved_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_thread_interactions_resolved_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "resolved_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_tree_hold_members": { + "name": "issue_tree_hold_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "hold_id": { + "name": "hold_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "parent_issue_id": { + "name": "parent_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "depth": { + "name": "depth", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "issue_identifier": { + "name": "issue_identifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_title": { + "name": "issue_title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "issue_status": { + "name": "issue_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "assignee_agent_id": { + "name": "assignee_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "assignee_user_id": { + "name": "assignee_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "active_run_id": { + "name": "active_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "active_run_status": { + "name": "active_run_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "skipped": { + "name": "skipped", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "skip_reason": { + "name": "skip_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_tree_hold_members_hold_issue_uq": { + "name": "issue_tree_hold_members_hold_issue_uq", + "columns": [ + { + "expression": "hold_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_tree_hold_members_company_issue_idx": { + "name": "issue_tree_hold_members_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_tree_hold_members_hold_depth_idx": { + "name": "issue_tree_hold_members_hold_depth_idx", + "columns": [ + { + "expression": "hold_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "depth", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_tree_hold_members_company_id_companies_id_fk": { + "name": "issue_tree_hold_members_company_id_companies_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_tree_hold_members_hold_id_issue_tree_holds_id_fk": { + "name": "issue_tree_hold_members_hold_id_issue_tree_holds_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "issue_tree_holds", + "columnsFrom": [ + "hold_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_tree_hold_members_issue_id_issues_id_fk": { + "name": "issue_tree_hold_members_issue_id_issues_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_tree_hold_members_parent_issue_id_issues_id_fk": { + "name": "issue_tree_hold_members_parent_issue_id_issues_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "issues", + "columnsFrom": [ + "parent_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_tree_hold_members_assignee_agent_id_agents_id_fk": { + "name": "issue_tree_hold_members_assignee_agent_id_agents_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "agents", + "columnsFrom": [ + "assignee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_tree_hold_members_active_run_id_heartbeat_runs_id_fk": { + "name": "issue_tree_hold_members_active_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "active_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_tree_holds": { + "name": "issue_tree_holds", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "root_issue_id": { + "name": "root_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "release_policy": { + "name": "release_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_by_actor_type": { + "name": "created_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "released_at": { + "name": "released_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "released_by_actor_type": { + "name": "released_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "released_by_agent_id": { + "name": "released_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "released_by_user_id": { + "name": "released_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "released_by_run_id": { + "name": "released_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "release_reason": { + "name": "release_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "release_metadata": { + "name": "release_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_tree_holds_company_root_status_idx": { + "name": "issue_tree_holds_company_root_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "root_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_tree_holds_company_status_mode_idx": { + "name": "issue_tree_holds_company_status_mode_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "mode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_tree_holds_company_id_companies_id_fk": { + "name": "issue_tree_holds_company_id_companies_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_tree_holds_root_issue_id_issues_id_fk": { + "name": "issue_tree_holds_root_issue_id_issues_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "issues", + "columnsFrom": [ + "root_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_tree_holds_created_by_agent_id_agents_id_fk": { + "name": "issue_tree_holds_created_by_agent_id_agents_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_tree_holds_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_tree_holds_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_tree_holds_released_by_agent_id_agents_id_fk": { + "name": "issue_tree_holds_released_by_agent_id_agents_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "agents", + "columnsFrom": [ + "released_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_tree_holds_released_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_tree_holds_released_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "released_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_watchdogs": { + "name": "issue_watchdogs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "watchdog_agent_id": { + "name": "watchdog_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "instructions": { + "name": "instructions", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "watchdog_issue_id": { + "name": "watchdog_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_observed_fingerprint": { + "name": "last_observed_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_reviewed_fingerprint": { + "name": "last_reviewed_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_observed_stop_snapshot": { + "name": "last_observed_stop_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "last_reviewed_stop_snapshot": { + "name": "last_reviewed_stop_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "last_triggered_at": { + "name": "last_triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_completed_at": { + "name": "last_completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "trigger_count": { + "name": "trigger_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_run_id": { + "name": "updated_by_run_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": { + "issue_watchdogs_company_issue_uq": { + "name": "issue_watchdogs_company_issue_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_watchdogs_company_status_idx": { + "name": "issue_watchdogs_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_watchdogs_company_agent_idx": { + "name": "issue_watchdogs_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "watchdog_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_watchdogs_company_watchdog_issue_uq": { + "name": "issue_watchdogs_company_watchdog_issue_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "watchdog_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issue_watchdogs\".\"watchdog_issue_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_watchdogs_company_id_companies_id_fk": { + "name": "issue_watchdogs_company_id_companies_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_watchdogs_issue_id_issues_id_fk": { + "name": "issue_watchdogs_issue_id_issues_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_watchdogs_watchdog_agent_id_agents_id_fk": { + "name": "issue_watchdogs_watchdog_agent_id_agents_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "agents", + "columnsFrom": [ + "watchdog_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_watchdogs_watchdog_issue_id_issues_id_fk": { + "name": "issue_watchdogs_watchdog_issue_id_issues_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "issues", + "columnsFrom": [ + "watchdog_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_watchdogs_created_by_agent_id_agents_id_fk": { + "name": "issue_watchdogs_created_by_agent_id_agents_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_watchdogs_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_watchdogs_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_watchdogs_updated_by_agent_id_agents_id_fk": { + "name": "issue_watchdogs_updated_by_agent_id_agents_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_watchdogs_updated_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_watchdogs_updated_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "updated_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_work_products": { + "name": "issue_work_products", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "runtime_service_id": { + "name": "runtime_service_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "review_state": { + "name": "review_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "source_trust": { + "name": "source_trust", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_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": { + "issue_work_products_company_issue_type_idx": { + "name": "issue_work_products_company_issue_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_execution_workspace_type_idx": { + "name": "issue_work_products_company_execution_workspace_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_provider_external_id_idx": { + "name": "issue_work_products_company_provider_external_id_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_updated_idx": { + "name": "issue_work_products_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_work_products_company_id_companies_id_fk": { + "name": "issue_work_products_company_id_companies_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_work_products_project_id_projects_id_fk": { + "name": "issue_work_products_project_id_projects_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_issue_id_issues_id_fk": { + "name": "issue_work_products_issue_id_issues_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_work_products_execution_workspace_id_execution_workspaces_id_fk": { + "name": "issue_work_products_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_runtime_service_id_workspace_runtime_services_id_fk": { + "name": "issue_work_products_runtime_service_id_workspace_runtime_services_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "workspace_runtime_services", + "columnsFrom": [ + "runtime_service_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_work_products_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issues": { + "name": "issues", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "conversation_agent_id": { + "name": "conversation_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "conversation_user_id": { + "name": "conversation_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "conversation_state": { + "name": "conversation_state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "conversation_session_generation": { + "name": "conversation_session_generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "conversation_boundary_comment_id": { + "name": "conversation_boundary_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'backlog'" + }, + "status_version": { + "name": "status_version", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_status_decision_id": { + "name": "last_status_decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "work_mode": { + "name": "work_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'standard'" + }, + "harness_kind": { + "name": "harness_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "review_policy": { + "name": "review_policy", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_agent_id": { + "name": "assignee_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "assignee_user_id": { + "name": "assignee_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "checkout_run_id": { + "name": "checkout_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_run_id": { + "name": "execution_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_agent_name_key": { + "name": "execution_agent_name_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_locked_at": { + "name": "execution_locked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_number": { + "name": "issue_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_kind": { + "name": "origin_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "origin_id": { + "name": "origin_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_run_id": { + "name": "origin_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_identity_context_id": { + "name": "origin_identity_context_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "continuation_identity_context_id": { + "name": "continuation_identity_context_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "origin_fingerprint": { + "name": "origin_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "request_depth": { + "name": "request_depth", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_adapter_overrides": { + "name": "assignee_adapter_overrides", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "execution_policy": { + "name": "execution_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "execution_state": { + "name": "execution_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "monitor_next_check_at": { + "name": "monitor_next_check_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "monitor_wake_requested_at": { + "name": "monitor_wake_requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "monitor_last_triggered_at": { + "name": "monitor_last_triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "monitor_attempt_count": { + "name": "monitor_attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "monitor_notes": { + "name": "monitor_notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "monitor_scheduled_by": { + "name": "monitor_scheduled_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_preference": { + "name": "execution_workspace_preference", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_settings": { + "name": "execution_workspace_settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "source_trust": { + "name": "source_trust", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "unblock_descriptor": { + "name": "unblock_descriptor", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "blocked_transition_at": { + "name": "blocked_transition_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "blocked_owner_notified_at": { + "name": "blocked_owner_notified_at", + "type": "timestamp with time zone", + "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 + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "hidden_at": { + "name": "hidden_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": { + "issues_conversation_identity_idx": { + "name": "issues_conversation_identity_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "conversation_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "conversation_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_status_idx": { + "name": "issues_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_harness_kind_idx": { + "name": "issues_company_harness_kind_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "harness_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_assignee_status_idx": { + "name": "issues_company_assignee_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_assignee_user_status_idx": { + "name": "issues_company_assignee_user_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_responsible_user_idx": { + "name": "issues_company_responsible_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "responsible_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_parent_idx": { + "name": "issues_company_parent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_project_idx": { + "name": "issues_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_origin_idx": { + "name": "issues_company_origin_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_project_workspace_idx": { + "name": "issues_company_project_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_execution_workspace_idx": { + "name": "issues_company_execution_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_monitor_due_idx": { + "name": "issues_company_monitor_due_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "monitor_next_check_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_updated_idx": { + "name": "issues_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_created_idx": { + "name": "issues_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_open_normalized_title_created_idx": { + "name": "issues_open_normalized_title_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(regexp_replace(btrim(\"title\"), '\\s+', ' ', 'g'))", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"issues\".\"hidden_at\" is null and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_priority_idx": { + "name": "issues_company_priority_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_identifier_idx": { + "name": "issues_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_title_search_idx": { + "name": "issues_title_search_idx", + "columns": [ + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "issues_identifier_search_idx": { + "name": "issues_identifier_search_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "issues_description_search_idx": { + "name": "issues_description_search_idx", + "columns": [ + { + "expression": "description", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "issues_open_routine_execution_uq": { + "name": "issues_open_routine_execution_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'routine_execution'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"execution_run_id\" is not null\n and \"issues\".\"status\" in ('backlog', 'todo', 'in_progress', 'in_review', 'blocked')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_active_liveness_recovery_incident_uq": { + "name": "issues_active_liveness_recovery_incident_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'harness_liveness_escalation'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_active_liveness_recovery_leaf_uq": { + "name": "issues_active_liveness_recovery_leaf_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'harness_liveness_escalation'\n and \"issues\".\"origin_fingerprint\" <> 'default'\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_active_stale_run_evaluation_uq": { + "name": "issues_active_stale_run_evaluation_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'stale_active_run_evaluation'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_active_task_watchdog_uq": { + "name": "issues_active_task_watchdog_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'task_watchdog'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_active_productivity_review_uq": { + "name": "issues_active_productivity_review_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'issue_productivity_review'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_active_stranded_issue_recovery_uq": { + "name": "issues_active_stranded_issue_recovery_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'stranded_issue_recovery'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_onboarding_first_task_uq": { + "name": "issues_onboarding_first_task_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'onboarding_first_task'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issues_company_id_companies_id_fk": { + "name": "issues_company_id_companies_id_fk", + "tableFrom": "issues", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_conversation_agent_id_agents_id_fk": { + "name": "issues_conversation_agent_id_agents_id_fk", + "tableFrom": "issues", + "tableTo": "agents", + "columnsFrom": [ + "conversation_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_project_id_projects_id_fk": { + "name": "issues_project_id_projects_id_fk", + "tableFrom": "issues", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_project_workspace_id_project_workspaces_id_fk": { + "name": "issues_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "issues", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_goal_id_goals_id_fk": { + "name": "issues_goal_id_goals_id_fk", + "tableFrom": "issues", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_parent_id_issues_id_fk": { + "name": "issues_parent_id_issues_id_fk", + "tableFrom": "issues", + "tableTo": "issues", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_assignee_agent_id_agents_id_fk": { + "name": "issues_assignee_agent_id_agents_id_fk", + "tableFrom": "issues", + "tableTo": "agents", + "columnsFrom": [ + "assignee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_checkout_run_id_heartbeat_runs_id_fk": { + "name": "issues_checkout_run_id_heartbeat_runs_id_fk", + "tableFrom": "issues", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "checkout_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_execution_run_id_heartbeat_runs_id_fk": { + "name": "issues_execution_run_id_heartbeat_runs_id_fk", + "tableFrom": "issues", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "execution_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_created_by_agent_id_agents_id_fk": { + "name": "issues_created_by_agent_id_agents_id_fk", + "tableFrom": "issues", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_execution_workspace_id_execution_workspaces_id_fk": { + "name": "issues_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "issues", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "issues_company_id_uq": { + "name": "issues_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "issues_conversation_identity_check": { + "name": "issues_conversation_identity_check", + "value": "(\n \"issues\".\"conversation_agent_id\" is null and \"issues\".\"conversation_user_id\" is null and \"issues\".\"conversation_state\" is null\n ) or (\n \"issues\".\"conversation_agent_id\" is not null and \"issues\".\"conversation_user_id\" is not null\n and \"issues\".\"assignee_agent_id\" = \"issues\".\"conversation_agent_id\" and \"issues\".\"assignee_agent_id\" is not null\n and \"issues\".\"assignee_user_id\" is null and \"issues\".\"conversation_state\" is not null\n and \"issues\".\"conversation_state\" in ('active', 'waiting')\n and \"issues\".\"status\" not in ('done', 'cancelled')\n )" + } + }, + "isRLSEnabled": false + }, + "public.join_requests": { + "name": "join_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "invite_id": { + "name": "invite_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "request_type": { + "name": "request_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending_approval'" + }, + "request_ip": { + "name": "request_ip", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requesting_user_id": { + "name": "requesting_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_email_snapshot": { + "name": "request_email_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_name": { + "name": "agent_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_defaults_payload": { + "name": "agent_defaults_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "claim_secret_hash": { + "name": "claim_secret_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "claim_secret_expires_at": { + "name": "claim_secret_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "claim_secret_consumed_at": { + "name": "claim_secret_consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_agent_id": { + "name": "created_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approved_by_user_id": { + "name": "approved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "approved_at": { + "name": "approved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "rejected_by_user_id": { + "name": "rejected_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rejected_at": { + "name": "rejected_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": { + "join_requests_invite_unique_idx": { + "name": "join_requests_invite_unique_idx", + "columns": [ + { + "expression": "invite_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "join_requests_company_status_type_created_idx": { + "name": "join_requests_company_status_type_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "join_requests_pending_human_user_uq": { + "name": "join_requests_pending_human_user_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requesting_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"join_requests\".\"request_type\" = 'human' AND \"join_requests\".\"status\" = 'pending_approval' AND \"join_requests\".\"requesting_user_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "join_requests_pending_human_email_uq": { + "name": "join_requests_pending_human_email_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"request_email_snapshot\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"join_requests\".\"request_type\" = 'human' AND \"join_requests\".\"status\" = 'pending_approval' AND \"join_requests\".\"request_email_snapshot\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "join_requests_invite_id_invites_id_fk": { + "name": "join_requests_invite_id_invites_id_fk", + "tableFrom": "join_requests", + "tableTo": "invites", + "columnsFrom": [ + "invite_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "join_requests_company_id_companies_id_fk": { + "name": "join_requests_company_id_companies_id_fk", + "tableFrom": "join_requests", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "join_requests_created_agent_id_agents_id_fk": { + "name": "join_requests_created_agent_id_agents_id_fk", + "tableFrom": "join_requests", + "tableTo": "agents", + "columnsFrom": [ + "created_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.labels": { + "name": "labels", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "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": { + "labels_company_idx": { + "name": "labels_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "labels_company_name_idx": { + "name": "labels_company_name_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "labels_company_id_companies_id_fk": { + "name": "labels_company_id_companies_id_fk", + "tableFrom": "labels", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.managed_agent_profiles": { + "name": "managed_agent_profiles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "profile_key": { + "name": "profile_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "service": { + "name": "service", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'anthropic_managed_agents'" + }, + "anthropic_agent_id": { + "name": "anthropic_agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_version": { + "name": "agent_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "environment_id": { + "name": "environment_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "beta_version": { + "name": "beta_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'managed-agents-2026-04-01'" + }, + "default_model": { + "name": "default_model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'claude-sonnet-5'" + }, + "default_max_list_cost_cents": { + "name": "default_max_list_cost_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 100 + }, + "api_key_secret_id": { + "name": "api_key_secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "retention_acknowledged": { + "name": "retention_acknowledged", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "qualification": { + "name": "qualification", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "qualified_at": { + "name": "qualified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "qualified_revision": { + "name": "qualified_revision", + "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": { + "managed_agent_profiles_company_idx": { + "name": "managed_agent_profiles_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "managed_agent_profiles_company_key_uq": { + "name": "managed_agent_profiles_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "profile_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "managed_agent_profiles_company_resource_uq": { + "name": "managed_agent_profiles_company_resource_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "anthropic_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_version", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "managed_agent_profiles_company_id_companies_id_fk": { + "name": "managed_agent_profiles_company_id_companies_id_fk", + "tableFrom": "managed_agent_profiles", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "managed_agent_profiles_api_key_secret_id_company_secrets_id_fk": { + "name": "managed_agent_profiles_api_key_secret_id_company_secrets_id_fk", + "tableFrom": "managed_agent_profiles", + "tableTo": "company_secrets", + "columnsFrom": [ + "api_key_secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "managed_agent_profiles_service_check": { + "name": "managed_agent_profiles_service_check", + "value": "\"managed_agent_profiles\".\"service\" = 'anthropic_managed_agents'" + }, + "managed_agent_profiles_beta_check": { + "name": "managed_agent_profiles_beta_check", + "value": "\"managed_agent_profiles\".\"beta_version\" = 'managed-agents-2026-04-01'" + }, + "managed_agent_profiles_positive_budget_check": { + "name": "managed_agent_profiles_positive_budget_check", + "value": "\"managed_agent_profiles\".\"default_max_list_cost_cents\" > 0" + }, + "managed_agent_profiles_qualified_revision_check": { + "name": "managed_agent_profiles_qualified_revision_check", + "value": "(\"managed_agent_profiles\".\"qualified_at\" IS NULL AND \"managed_agent_profiles\".\"qualified_revision\" IS NULL) OR (\"managed_agent_profiles\".\"qualified_at\" IS NOT NULL AND \"managed_agent_profiles\".\"qualification\" <> '{}'::jsonb AND \"managed_agent_profiles\".\"qualified_revision\" ~ '^sha256:[0-9a-f]{64}$')" + } + }, + "isRLSEnabled": false + }, + "public.native_run_finalizations": { + "name": "native_run_finalizations", + "schema": "", + "columns": { + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "attempt": { + "name": "attempt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "lease_owner": { + "name": "lease_owner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lease_expires_at": { + "name": "lease_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "controller_boot_id": { + "name": "controller_boot_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "controller_pid": { + "name": "controller_pid", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "controller_process_started_at": { + "name": "controller_process_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "controller_generation": { + "name": "controller_generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "recovery_state": { + "name": "recovery_state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "recovery_request_id": { + "name": "recovery_request_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "recovery_history": { + "name": "recovery_history", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "result_id": { + "name": "result_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "assessment_id": { + "name": "assessment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "decision_id": { + "name": "decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "failure_code": { + "name": "failure_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "failure_detail": { + "name": "failure_detail", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "control_deadline_at": { + "name": "control_deadline_at", + "type": "timestamp with time zone", + "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": { + "native_run_finalizations_control_deadline_idx": { + "name": "native_run_finalizations_control_deadline_idx", + "columns": [ + { + "expression": "control_deadline_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"native_run_finalizations\".\"control_deadline_at\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "native_run_finalizations_company_id_companies_id_fk": { + "name": "native_run_finalizations_company_id_companies_id_fk", + "tableFrom": "native_run_finalizations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_finalizations_issue_company_fk": { + "name": "native_run_finalizations_issue_company_fk", + "tableFrom": "native_run_finalizations", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_finalizations_run_owner_fk": { + "name": "native_run_finalizations_run_owner_fk", + "tableFrom": "native_run_finalizations", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id" + ], + "columnsTo": [ + "company_id", + "native_issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_finalizations_result_owner_fk": { + "name": "native_run_finalizations_result_owner_fk", + "tableFrom": "native_run_finalizations", + "tableTo": "native_run_results", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "result_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "run_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_finalizations_assessment_owner_fk": { + "name": "native_run_finalizations_assessment_owner_fk", + "tableFrom": "native_run_finalizations", + "tableTo": "work_assessments", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "assessment_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "run_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_finalizations_decision_owner_fk": { + "name": "native_run_finalizations_decision_owner_fk", + "tableFrom": "native_run_finalizations", + "tableTo": "status_decisions", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "assessment_id", + "decision_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "run_id", + "assessment_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "native_run_finalizations_assessment_requires_result_check": { + "name": "native_run_finalizations_assessment_requires_result_check", + "value": "\"native_run_finalizations\".\"assessment_id\" is null or \"native_run_finalizations\".\"result_id\" is not null" + }, + "native_run_finalizations_decision_requires_assessment_check": { + "name": "native_run_finalizations_decision_requires_assessment_check", + "value": "\"native_run_finalizations\".\"decision_id\" is null or \"native_run_finalizations\".\"assessment_id\" is not null" + } + }, + "isRLSEnabled": false + }, + "public.native_run_results": { + "name": "native_run_results", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "turn_id": { + "name": "turn_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completion_contract_id": { + "name": "completion_contract_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "caller_result_id": { + "name": "caller_result_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "caller_dedupe_key": { + "name": "caller_dedupe_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "server_fingerprint": { + "name": "server_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schema_status": { + "name": "schema_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rejection_code": { + "name": "rejection_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_json": { + "name": "result_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "canonical_sha256": { + "name": "canonical_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "native_run_results_run_fingerprint_uq": { + "name": "native_run_results_run_fingerprint_uq", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "server_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "native_run_results_run_caller_result_uq": { + "name": "native_run_results_run_caller_result_uq", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "caller_result_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "native_run_results_run_caller_dedupe_uq": { + "name": "native_run_results_run_caller_dedupe_uq", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "caller_dedupe_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "native_run_results_company_id_companies_id_fk": { + "name": "native_run_results_company_id_companies_id_fk", + "tableFrom": "native_run_results", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_results_issue_company_fk": { + "name": "native_run_results_issue_company_fk", + "tableFrom": "native_run_results", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_results_run_contract_owner_fk": { + "name": "native_run_results_run_contract_owner_fk", + "tableFrom": "native_run_results", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "completion_contract_id" + ], + "columnsTo": [ + "company_id", + "native_issue_id", + "id", + "completion_contract_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_results_completion_contract_owner_fk": { + "name": "native_run_results_completion_contract_owner_fk", + "tableFrom": "native_run_results", + "tableTo": "completion_contracts", + "columnsFrom": [ + "company_id", + "issue_id", + "completion_contract_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "native_run_results_company_issue_run_id_uq": { + "name": "native_run_results_company_issue_run_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "issue_id", + "run_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pipeline_automation_executions": { + "name": "pipeline_automation_executions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "automation_id": { + "name": "automation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "triggering_event_id": { + "name": "triggering_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_issue_id": { + "name": "execution_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "retry_of_execution_id": { + "name": "retry_of_execution_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "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": { + "pipeline_automation_executions_idempotency_uq": { + "name": "pipeline_automation_executions_idempotency_uq", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "automation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "triggering_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_automation_executions_company_case_idx": { + "name": "pipeline_automation_executions_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_automation_executions_routine_idx": { + "name": "pipeline_automation_executions_routine_idx", + "columns": [ + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_automation_executions_execution_issue_idx": { + "name": "pipeline_automation_executions_execution_issue_idx", + "columns": [ + { + "expression": "execution_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_automation_executions_retry_of_execution_idx": { + "name": "pipeline_automation_executions_retry_of_execution_idx", + "columns": [ + { + "expression": "retry_of_execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_automation_executions_company_id_companies_id_fk": { + "name": "pipeline_automation_executions_company_id_companies_id_fk", + "tableFrom": "pipeline_automation_executions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_automation_executions_case_id_pipeline_cases_id_fk": { + "name": "pipeline_automation_executions_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_automation_executions", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_automation_executions_routine_id_routines_id_fk": { + "name": "pipeline_automation_executions_routine_id_routines_id_fk", + "tableFrom": "pipeline_automation_executions", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_automation_executions_execution_issue_id_issues_id_fk": { + "name": "pipeline_automation_executions_execution_issue_id_issues_id_fk", + "tableFrom": "pipeline_automation_executions", + "tableTo": "issues", + "columnsFrom": [ + "execution_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pipeline_automation_executions_status_check": { + "name": "pipeline_automation_executions_status_check", + "value": "\"pipeline_automation_executions\".\"status\" in ('succeeded', 'failed')" + } + }, + "isRLSEnabled": false + }, + "public.pipeline_case_blockers": { + "name": "pipeline_case_blockers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "blocked_by_case_id": { + "name": "blocked_by_case_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": { + "pipeline_case_blockers_case_blocked_by_uq": { + "name": "pipeline_case_blockers_case_blocked_by_uq", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "blocked_by_case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_blockers_blocked_by_idx": { + "name": "pipeline_case_blockers_blocked_by_idx", + "columns": [ + { + "expression": "blocked_by_case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_blockers_company_case_idx": { + "name": "pipeline_case_blockers_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_case_blockers_company_id_companies_id_fk": { + "name": "pipeline_case_blockers_company_id_companies_id_fk", + "tableFrom": "pipeline_case_blockers", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_blockers_case_id_pipeline_cases_id_fk": { + "name": "pipeline_case_blockers_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_case_blockers", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_blockers_blocked_by_case_id_pipeline_cases_id_fk": { + "name": "pipeline_case_blockers_blocked_by_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_case_blockers", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "blocked_by_case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pipeline_case_blockers_no_self_block_check": { + "name": "pipeline_case_blockers_no_self_block_check", + "value": "\"pipeline_case_blockers\".\"case_id\" <> \"pipeline_case_blockers\".\"blocked_by_case_id\"" + } + }, + "isRLSEnabled": false + }, + "public.pipeline_case_documents": { + "name": "pipeline_case_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "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": { + "pipeline_case_documents_company_case_key_uq": { + "name": "pipeline_case_documents_company_case_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_documents_document_uq": { + "name": "pipeline_case_documents_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_documents_company_case_updated_idx": { + "name": "pipeline_case_documents_company_case_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_case_documents_company_id_companies_id_fk": { + "name": "pipeline_case_documents_company_id_companies_id_fk", + "tableFrom": "pipeline_case_documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_documents_case_id_pipeline_cases_id_fk": { + "name": "pipeline_case_documents_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_case_documents", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_documents_document_id_documents_id_fk": { + "name": "pipeline_case_documents_document_id_documents_id_fk", + "tableFrom": "pipeline_case_documents", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pipeline_case_events": { + "name": "pipeline_case_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_agent_id": { + "name": "actor_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "from_stage_id": { + "name": "from_stage_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "to_stage_id": { + "name": "to_stage_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "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()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_case_events_case_created_idx": { + "name": "pipeline_case_events_case_created_idx", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_events_company_case_idx": { + "name": "pipeline_case_events_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_case_events_company_id_companies_id_fk": { + "name": "pipeline_case_events_company_id_companies_id_fk", + "tableFrom": "pipeline_case_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_events_case_id_pipeline_cases_id_fk": { + "name": "pipeline_case_events_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_case_events", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_events_actor_agent_id_agents_id_fk": { + "name": "pipeline_case_events_actor_agent_id_agents_id_fk", + "tableFrom": "pipeline_case_events", + "tableTo": "agents", + "columnsFrom": [ + "actor_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "pipeline_case_events_from_stage_id_pipeline_stages_id_fk": { + "name": "pipeline_case_events_from_stage_id_pipeline_stages_id_fk", + "tableFrom": "pipeline_case_events", + "tableTo": "pipeline_stages", + "columnsFrom": [ + "from_stage_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "pipeline_case_events_to_stage_id_pipeline_stages_id_fk": { + "name": "pipeline_case_events_to_stage_id_pipeline_stages_id_fk", + "tableFrom": "pipeline_case_events", + "tableTo": "pipeline_stages", + "columnsFrom": [ + "to_stage_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pipeline_case_events_type_check": { + "name": "pipeline_case_events_type_check", + "value": "\"pipeline_case_events\".\"type\" in (\n 'ingested',\n 'updated',\n 'claimed',\n 'lease_released',\n 'lease_expired',\n 'transitioned',\n 'transition_forced',\n 'transition_suggested',\n 'suggestion_resolved',\n 'review_decided',\n 'conversation_opened',\n 'issue_linked',\n 'issue_unlinked',\n 'automation_executed',\n 'automation_failed',\n 'automation_retry_requested',\n 'automation_effects_retired',\n 'automation_retry_dispatched',\n 'blockers_set',\n 'blockers_resolved',\n 'children_terminal',\n 'upstream_drift',\n 'drift_acknowledged'\n )" + }, + "pipeline_case_events_actor_type_check": { + "name": "pipeline_case_events_actor_type_check", + "value": "\"pipeline_case_events\".\"actor_type\" in ('user', 'agent', 'system')" + }, + "pipeline_case_events_agent_run_check": { + "name": "pipeline_case_events_agent_run_check", + "value": "\"pipeline_case_events\".\"actor_type\" <> 'agent' or \"pipeline_case_events\".\"run_id\" is not null" + } + }, + "isRLSEnabled": false + }, + "public.pipeline_case_issue_links": { + "name": "pipeline_case_issue_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "automation_attempt_id": { + "name": "automation_attempt_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "retired_at": { + "name": "retired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "retired_by_attempt_id": { + "name": "retired_by_attempt_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "retired_reason": { + "name": "retired_reason", + "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": { + "pipeline_case_issue_links_case_issue_uq": { + "name": "pipeline_case_issue_links_case_issue_uq", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_issue_links_issue_idx": { + "name": "pipeline_case_issue_links_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_issue_links_company_case_idx": { + "name": "pipeline_case_issue_links_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_issue_links_automation_attempt_idx": { + "name": "pipeline_case_issue_links_automation_attempt_idx", + "columns": [ + { + "expression": "automation_attempt_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_case_issue_links_company_id_companies_id_fk": { + "name": "pipeline_case_issue_links_company_id_companies_id_fk", + "tableFrom": "pipeline_case_issue_links", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_issue_links_case_id_pipeline_cases_id_fk": { + "name": "pipeline_case_issue_links_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_case_issue_links", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_issue_links_issue_id_issues_id_fk": { + "name": "pipeline_case_issue_links_issue_id_issues_id_fk", + "tableFrom": "pipeline_case_issue_links", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pipeline_case_issue_links_role_check": { + "name": "pipeline_case_issue_links_role_check", + "value": "\"pipeline_case_issue_links\".\"role\" in ('origin', 'conversation', 'work', 'automation')" + } + }, + "isRLSEnabled": false + }, + "public.pipeline_cases": { + "name": "pipeline_cases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pipeline_id": { + "name": "pipeline_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "stage_id": { + "name": "stage_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_key": { + "name": "case_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fields": { + "name": "fields", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "workspace_ref": { + "name": "workspace_ref", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "parent_case_id": { + "name": "parent_case_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_case_version": { + "name": "parent_case_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "request_key": { + "name": "request_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "automation_attempt_id": { + "name": "automation_attempt_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "pending_suggestion": { + "name": "pending_suggestion", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "lease_owner_type": { + "name": "lease_owner_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lease_agent_id": { + "name": "lease_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "lease_user_id": { + "name": "lease_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lease_token": { + "name": "lease_token", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "lease_expires_at": { + "name": "lease_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "terminal_kind": { + "name": "terminal_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "terminal_at": { + "name": "terminal_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "retired_at": { + "name": "retired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "retired_by_attempt_id": { + "name": "retired_by_attempt_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "retired_reason": { + "name": "retired_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "hidden_from_board_at": { + "name": "hidden_from_board_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "child_count": { + "name": "child_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "terminal_child_count": { + "name": "terminal_child_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "origin_run_id": { + "name": "origin_run_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": { + "pipeline_cases_pipeline_case_key_uq": { + "name": "pipeline_cases_pipeline_case_key_uq", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_parent_request_key_uq": { + "name": "pipeline_cases_parent_request_key_uq", + "columns": [ + { + "expression": "parent_case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"pipeline_cases\".\"request_key\" is not null and \"pipeline_cases\".\"retired_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_company_idx": { + "name": "pipeline_cases_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_pipeline_stage_idx": { + "name": "pipeline_cases_pipeline_stage_idx", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_parent_idx": { + "name": "pipeline_cases_parent_idx", + "columns": [ + { + "expression": "parent_case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_automation_attempt_idx": { + "name": "pipeline_cases_automation_attempt_idx", + "columns": [ + { + "expression": "automation_attempt_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_retired_idx": { + "name": "pipeline_cases_retired_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "retired_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_lease_expires_idx": { + "name": "pipeline_cases_lease_expires_idx", + "columns": [ + { + "expression": "lease_expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"pipeline_cases\".\"lease_expires_at\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_cases_company_id_companies_id_fk": { + "name": "pipeline_cases_company_id_companies_id_fk", + "tableFrom": "pipeline_cases", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_cases_pipeline_id_pipelines_id_fk": { + "name": "pipeline_cases_pipeline_id_pipelines_id_fk", + "tableFrom": "pipeline_cases", + "tableTo": "pipelines", + "columnsFrom": [ + "pipeline_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_cases_stage_id_pipeline_stages_id_fk": { + "name": "pipeline_cases_stage_id_pipeline_stages_id_fk", + "tableFrom": "pipeline_cases", + "tableTo": "pipeline_stages", + "columnsFrom": [ + "stage_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "pipeline_cases_parent_case_id_pipeline_cases_id_fk": { + "name": "pipeline_cases_parent_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_cases", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "parent_case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "pipeline_cases_lease_agent_id_agents_id_fk": { + "name": "pipeline_cases_lease_agent_id_agents_id_fk", + "tableFrom": "pipeline_cases", + "tableTo": "agents", + "columnsFrom": [ + "lease_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "pipeline_cases_created_by_agent_id_agents_id_fk": { + "name": "pipeline_cases_created_by_agent_id_agents_id_fk", + "tableFrom": "pipeline_cases", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pipeline_cases_terminal_kind_check": { + "name": "pipeline_cases_terminal_kind_check", + "value": "\"pipeline_cases\".\"terminal_kind\" is null or \"pipeline_cases\".\"terminal_kind\" in ('done', 'cancelled')" + }, + "pipeline_cases_lease_owner_type_check": { + "name": "pipeline_cases_lease_owner_type_check", + "value": "\"pipeline_cases\".\"lease_owner_type\" is null or \"pipeline_cases\".\"lease_owner_type\" in ('user', 'agent')" + } + }, + "isRLSEnabled": false + }, + "public.pipeline_documents": { + "name": "pipeline_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pipeline_id": { + "name": "pipeline_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "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": { + "pipeline_documents_company_pipeline_key_uq": { + "name": "pipeline_documents_company_pipeline_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_documents_document_uq": { + "name": "pipeline_documents_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_documents_company_pipeline_updated_idx": { + "name": "pipeline_documents_company_pipeline_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_documents_company_id_companies_id_fk": { + "name": "pipeline_documents_company_id_companies_id_fk", + "tableFrom": "pipeline_documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_documents_pipeline_id_pipelines_id_fk": { + "name": "pipeline_documents_pipeline_id_pipelines_id_fk", + "tableFrom": "pipeline_documents", + "tableTo": "pipelines", + "columnsFrom": [ + "pipeline_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_documents_document_id_documents_id_fk": { + "name": "pipeline_documents_document_id_documents_id_fk", + "tableFrom": "pipeline_documents", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pipeline_stages": { + "name": "pipeline_stages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "pipeline_id": { + "name": "pipeline_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_stages_pipeline_key_uq": { + "name": "pipeline_stages_pipeline_key_uq", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_stages_pipeline_position_idx": { + "name": "pipeline_stages_pipeline_position_idx", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_stages_pipeline_id_pipelines_id_fk": { + "name": "pipeline_stages_pipeline_id_pipelines_id_fk", + "tableFrom": "pipeline_stages", + "tableTo": "pipelines", + "columnsFrom": [ + "pipeline_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pipeline_stages_kind_check": { + "name": "pipeline_stages_kind_check", + "value": "\"pipeline_stages\".\"kind\" in ('working', 'review', 'done', 'cancelled')" + } + }, + "isRLSEnabled": false + }, + "public.pipeline_transitions": { + "name": "pipeline_transitions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "pipeline_id": { + "name": "pipeline_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "from_stage_id": { + "name": "from_stage_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "to_stage_id": { + "name": "to_stage_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "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": { + "pipeline_transitions_pipeline_edge_uq": { + "name": "pipeline_transitions_pipeline_edge_uq", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "from_stage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "to_stage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_transitions_pipeline_from_idx": { + "name": "pipeline_transitions_pipeline_from_idx", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "from_stage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_transitions_pipeline_to_idx": { + "name": "pipeline_transitions_pipeline_to_idx", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "to_stage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_transitions_pipeline_id_pipelines_id_fk": { + "name": "pipeline_transitions_pipeline_id_pipelines_id_fk", + "tableFrom": "pipeline_transitions", + "tableTo": "pipelines", + "columnsFrom": [ + "pipeline_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_transitions_from_stage_id_pipeline_stages_id_fk": { + "name": "pipeline_transitions_from_stage_id_pipeline_stages_id_fk", + "tableFrom": "pipeline_transitions", + "tableTo": "pipeline_stages", + "columnsFrom": [ + "from_stage_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_transitions_to_stage_id_pipeline_stages_id_fk": { + "name": "pipeline_transitions_to_stage_id_pipeline_stages_id_fk", + "tableFrom": "pipeline_transitions", + "tableTo": "pipeline_stages", + "columnsFrom": [ + "to_stage_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pipelines": { + "name": "pipelines", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enforce_transitions": { + "name": "enforce_transitions", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "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": { + "pipelines_company_key_uq": { + "name": "pipelines_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipelines_company_idx": { + "name": "pipelines_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipelines_company_project_idx": { + "name": "pipelines_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipelines_company_id_companies_id_fk": { + "name": "pipelines_company_id_companies_id_fk", + "tableFrom": "pipelines", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipelines_project_id_projects_id_fk": { + "name": "pipelines_project_id_projects_id_fk", + "tableFrom": "pipelines", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "pipelines_created_by_agent_id_agents_id_fk": { + "name": "pipelines_created_by_agent_id_agents_id_fk", + "tableFrom": "pipelines", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_company_settings": { + "name": "plugin_company_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "settings_json": { + "name": "settings_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "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": { + "plugin_company_settings_company_idx": { + "name": "plugin_company_settings_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_company_settings_plugin_idx": { + "name": "plugin_company_settings_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_company_settings_company_plugin_uq": { + "name": "plugin_company_settings_company_plugin_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_company_settings_company_id_companies_id_fk": { + "name": "plugin_company_settings_company_id_companies_id_fk", + "tableFrom": "plugin_company_settings", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_company_settings_plugin_id_plugins_id_fk": { + "name": "plugin_company_settings_plugin_id_plugins_id_fk", + "tableFrom": "plugin_company_settings", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_config": { + "name": "plugin_config", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "config_json": { + "name": "config_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "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": { + "plugin_config_plugin_company_idx": { + "name": "plugin_config_plugin_company_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_config_plugin_id_plugins_id_fk": { + "name": "plugin_config_plugin_id_plugins_id_fk", + "tableFrom": "plugin_config", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_config_company_id_companies_id_fk": { + "name": "plugin_config_company_id_companies_id_fk", + "tableFrom": "plugin_config", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_database_namespaces": { + "name": "plugin_database_namespaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_key": { + "name": "plugin_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "namespace_name": { + "name": "namespace_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "namespace_mode": { + "name": "namespace_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'schema'" + }, + "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()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_database_namespaces_plugin_idx": { + "name": "plugin_database_namespaces_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_database_namespaces_namespace_idx": { + "name": "plugin_database_namespaces_namespace_idx", + "columns": [ + { + "expression": "namespace_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_database_namespaces_status_idx": { + "name": "plugin_database_namespaces_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_database_namespaces_plugin_id_plugins_id_fk": { + "name": "plugin_database_namespaces_plugin_id_plugins_id_fk", + "tableFrom": "plugin_database_namespaces", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_entities": { + "name": "plugin_entities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_kind": { + "name": "scope_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_entities_plugin_idx": { + "name": "plugin_entities_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_company_idx": { + "name": "plugin_entities_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_type_idx": { + "name": "plugin_entities_type_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_scope_idx": { + "name": "plugin_entities_scope_idx", + "columns": [ + { + "expression": "scope_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_entities_plugin_id_plugins_id_fk": { + "name": "plugin_entities_plugin_id_plugins_id_fk", + "tableFrom": "plugin_entities", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_entities_company_id_companies_id_fk": { + "name": "plugin_entities_company_id_companies_id_fk", + "tableFrom": "plugin_entities", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "plugin_entities_external_idx": { + "name": "plugin_entities_external_idx", + "nullsNotDistinct": true, + "columns": [ + "company_id", + "plugin_id", + "entity_type", + "external_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_job_runs": { + "name": "plugin_job_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "job_id": { + "name": "job_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "logs": { + "name": "logs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_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": { + "plugin_job_runs_job_idx": { + "name": "plugin_job_runs_job_idx", + "columns": [ + { + "expression": "job_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_job_runs_plugin_idx": { + "name": "plugin_job_runs_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_job_runs_company_idx": { + "name": "plugin_job_runs_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_job_runs_status_idx": { + "name": "plugin_job_runs_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_job_runs_job_id_plugin_jobs_id_fk": { + "name": "plugin_job_runs_job_id_plugin_jobs_id_fk", + "tableFrom": "plugin_job_runs", + "tableTo": "plugin_jobs", + "columnsFrom": [ + "job_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_job_runs_plugin_id_plugins_id_fk": { + "name": "plugin_job_runs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_job_runs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_job_runs_company_id_companies_id_fk": { + "name": "plugin_job_runs_company_id_companies_id_fk", + "tableFrom": "plugin_job_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_jobs": { + "name": "plugin_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "job_key": { + "name": "job_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schedule": { + "name": "schedule", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "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": { + "plugin_jobs_plugin_idx": { + "name": "plugin_jobs_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_jobs_next_run_idx": { + "name": "plugin_jobs_next_run_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_jobs_unique_idx": { + "name": "plugin_jobs_unique_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "job_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_jobs_plugin_id_plugins_id_fk": { + "name": "plugin_jobs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_jobs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_logs": { + "name": "plugin_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'info'" + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "meta": { + "name": "meta", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_logs_plugin_time_idx": { + "name": "plugin_logs_plugin_time_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_logs_company_idx": { + "name": "plugin_logs_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_logs_level_idx": { + "name": "plugin_logs_level_idx", + "columns": [ + { + "expression": "level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_logs_plugin_id_plugins_id_fk": { + "name": "plugin_logs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_logs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_logs_company_id_companies_id_fk": { + "name": "plugin_logs_company_id_companies_id_fk", + "tableFrom": "plugin_logs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_managed_resources": { + "name": "plugin_managed_resources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_key": { + "name": "plugin_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_kind": { + "name": "resource_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_key": { + "name": "resource_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "defaults_json": { + "name": "defaults_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_managed_resources_company_idx": { + "name": "plugin_managed_resources_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_managed_resources_plugin_idx": { + "name": "plugin_managed_resources_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_managed_resources_resource_idx": { + "name": "plugin_managed_resources_resource_idx", + "columns": [ + { + "expression": "resource_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_managed_resources_company_plugin_resource_uq": { + "name": "plugin_managed_resources_company_plugin_resource_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_managed_resources_company_id_companies_id_fk": { + "name": "plugin_managed_resources_company_id_companies_id_fk", + "tableFrom": "plugin_managed_resources", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_managed_resources_plugin_id_plugins_id_fk": { + "name": "plugin_managed_resources_plugin_id_plugins_id_fk", + "tableFrom": "plugin_managed_resources", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_migrations": { + "name": "plugin_migrations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_key": { + "name": "plugin_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "namespace_name": { + "name": "namespace_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "migration_key": { + "name": "migration_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "checksum": { + "name": "checksum", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "plugin_version": { + "name": "plugin_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "applied_at": { + "name": "applied_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "plugin_migrations_plugin_key_idx": { + "name": "plugin_migrations_plugin_key_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "migration_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_migrations_plugin_idx": { + "name": "plugin_migrations_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_migrations_status_idx": { + "name": "plugin_migrations_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_migrations_plugin_id_plugins_id_fk": { + "name": "plugin_migrations_plugin_id_plugins_id_fk", + "tableFrom": "plugin_migrations", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_state": { + "name": "plugin_state", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_kind": { + "name": "scope_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "namespace": { + "name": "namespace", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "state_key": { + "name": "state_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value_json": { + "name": "value_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_state_plugin_scope_idx": { + "name": "plugin_state_plugin_scope_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_state_plugin_id_plugins_id_fk": { + "name": "plugin_state_plugin_id_plugins_id_fk", + "tableFrom": "plugin_state", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "plugin_state_unique_entry_idx": { + "name": "plugin_state_unique_entry_idx", + "nullsNotDistinct": true, + "columns": [ + "plugin_id", + "scope_kind", + "scope_id", + "namespace", + "state_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_webhook_deliveries": { + "name": "plugin_webhook_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "webhook_key": { + "name": "webhook_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "headers": { + "name": "headers", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_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": { + "plugin_webhook_deliveries_plugin_idx": { + "name": "plugin_webhook_deliveries_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_webhook_deliveries_company_idx": { + "name": "plugin_webhook_deliveries_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_webhook_deliveries_status_idx": { + "name": "plugin_webhook_deliveries_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_webhook_deliveries_key_idx": { + "name": "plugin_webhook_deliveries_key_idx", + "columns": [ + { + "expression": "webhook_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_webhook_deliveries_plugin_id_plugins_id_fk": { + "name": "plugin_webhook_deliveries_plugin_id_plugins_id_fk", + "tableFrom": "plugin_webhook_deliveries", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_webhook_deliveries_company_id_companies_id_fk": { + "name": "plugin_webhook_deliveries_company_id_companies_id_fk", + "tableFrom": "plugin_webhook_deliveries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugins": { + "name": "plugins", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_key": { + "name": "plugin_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "package_name": { + "name": "package_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "api_version": { + "name": "api_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "categories": { + "name": "categories", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "manifest_json": { + "name": "manifest_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'installed'" + }, + "install_order": { + "name": "install_order", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "package_path": { + "name": "package_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "installed_at": { + "name": "installed_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": { + "plugins_plugin_key_idx": { + "name": "plugins_plugin_key_idx", + "columns": [ + { + "expression": "plugin_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugins_status_idx": { + "name": "plugins_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.principal_permission_grants": { + "name": "principal_permission_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "principal_type": { + "name": "principal_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission_key": { + "name": "permission_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "granted_by_user_id": { + "name": "granted_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "principal_permission_grants_unique_idx": { + "name": "principal_permission_grants_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "principal_permission_grants_company_permission_idx": { + "name": "principal_permission_grants_company_permission_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "principal_permission_grants_company_id_companies_id_fk": { + "name": "principal_permission_grants_company_id_companies_id_fk", + "tableFrom": "principal_permission_grants", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_goals": { + "name": "project_goals", + "schema": "", + "columns": { + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_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": { + "project_goals_project_idx": { + "name": "project_goals_project_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_goals_goal_idx": { + "name": "project_goals_goal_idx", + "columns": [ + { + "expression": "goal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_goals_company_idx": { + "name": "project_goals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_goals_project_id_projects_id_fk": { + "name": "project_goals_project_id_projects_id_fk", + "tableFrom": "project_goals", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_goals_goal_id_goals_id_fk": { + "name": "project_goals_goal_id_goals_id_fk", + "tableFrom": "project_goals", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_goals_company_id_companies_id_fk": { + "name": "project_goals_company_id_companies_id_fk", + "tableFrom": "project_goals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "project_goals_project_id_goal_id_pk": { + "name": "project_goals_project_id_goal_id_pk", + "columns": [ + "project_id", + "goal_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_memberships": { + "name": "project_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'joined'" + }, + "starred_at": { + "name": "starred_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": { + "project_memberships_company_user_idx": { + "name": "project_memberships_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_memberships_company_user_starred_idx": { + "name": "project_memberships_company_user_starred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "starred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_memberships_project_idx": { + "name": "project_memberships_project_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_memberships_company_user_project_uq": { + "name": "project_memberships_company_user_project_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_memberships_company_id_companies_id_fk": { + "name": "project_memberships_company_id_companies_id_fk", + "tableFrom": "project_memberships", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_memberships_project_id_projects_id_fk": { + "name": "project_memberships_project_id_projects_id_fk", + "tableFrom": "project_memberships", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_workspaces": { + "name": "project_workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_path'" + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_url": { + "name": "repo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_ref": { + "name": "repo_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "default_ref": { + "name": "default_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "visibility": { + "name": "visibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "setup_command": { + "name": "setup_command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cleanup_command": { + "name": "cleanup_command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "remote_provider": { + "name": "remote_provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "remote_workspace_ref": { + "name": "remote_workspace_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shared_workspace_key": { + "name": "shared_workspace_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_primary": { + "name": "is_primary", + "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": { + "project_workspaces_company_project_idx": { + "name": "project_workspaces_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_primary_idx": { + "name": "project_workspaces_project_primary_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_primary", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_source_type_idx": { + "name": "project_workspaces_project_source_type_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_company_shared_key_idx": { + "name": "project_workspaces_company_shared_key_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "shared_workspace_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_remote_ref_idx": { + "name": "project_workspaces_project_remote_ref_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "remote_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "remote_workspace_ref", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_workspaces_company_id_companies_id_fk": { + "name": "project_workspaces_company_id_companies_id_fk", + "tableFrom": "project_workspaces", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "project_workspaces_project_id_projects_id_fk": { + "name": "project_workspaces_project_id_projects_id_fk", + "tableFrom": "project_workspaces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.projects": { + "name": "projects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'backlog'" + }, + "lead_agent_id": { + "name": "lead_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_date": { + "name": "target_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_policy": { + "name": "execution_workspace_policy", + "type": "jsonb", + "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": { + "projects_company_idx": { + "name": "projects_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "projects_company_id_companies_id_fk": { + "name": "projects_company_id_companies_id_fk", + "tableFrom": "projects", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "projects_goal_id_goals_id_fk": { + "name": "projects_goal_id_goals_id_fk", + "tableFrom": "projects", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "projects_lead_agent_id_agents_id_fk": { + "name": "projects_lead_agent_id_agents_id_fk", + "tableFrom": "projects", + "tableTo": "agents", + "columnsFrom": [ + "lead_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_trace_records": { + "name": "provider_trace_records", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'capturing'" + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trace_ref": { + "name": "trace_ref", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "frame_count": { + "name": "frame_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "byte_count": { + "name": "byte_count", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "digest": { + "name": "digest", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_by": { + "name": "requested_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_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": { + "provider_trace_records_run_unique": { + "name": "provider_trace_records_run_unique", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "provider_trace_records_expiry_idx": { + "name": "provider_trace_records_expiry_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "provider_trace_records_company_created_idx": { + "name": "provider_trace_records_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "provider_trace_records_company_id_companies_id_fk": { + "name": "provider_trace_records_company_id_companies_id_fk", + "tableFrom": "provider_trace_records", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "provider_trace_records_run_id_heartbeat_runs_id_fk": { + "name": "provider_trace_records_run_id_heartbeat_runs_id_fk", + "tableFrom": "provider_trace_records", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.remote_agent_profiles": { + "name": "remote_agent_profiles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "profile_key": { + "name": "profile_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "service": { + "name": "service", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "configuration": { + "name": "configuration", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "retention_acknowledged": { + "name": "retention_acknowledged", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "qualification": { + "name": "qualification", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "qualified_at": { + "name": "qualified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "qualified_revision": { + "name": "qualified_revision", + "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": { + "remote_agent_profiles_company_idx": { + "name": "remote_agent_profiles_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "remote_agent_profiles_company_key_uq": { + "name": "remote_agent_profiles_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "profile_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "remote_agent_profiles_company_id_companies_id_fk": { + "name": "remote_agent_profiles_company_id_companies_id_fk", + "tableFrom": "remote_agent_profiles", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "remote_agent_profiles_service_check": { + "name": "remote_agent_profiles_service_check", + "value": "\"remote_agent_profiles\".\"service\" = 'aws_bedrock_agentcore_harness'" + }, + "remote_agent_profiles_qualified_revision_check": { + "name": "remote_agent_profiles_qualified_revision_check", + "value": "(\"remote_agent_profiles\".\"qualified_at\" IS NULL AND \"remote_agent_profiles\".\"qualified_revision\" IS NULL) OR (\"remote_agent_profiles\".\"qualified_at\" IS NOT NULL AND \"remote_agent_profiles\".\"qualification\" <> '{}'::jsonb AND \"remote_agent_profiles\".\"qualified_revision\" ~ '^sha256:[0-9a-f]{64}$')" + } + }, + "isRLSEnabled": false + }, + "public.routine_documents": { + "name": "routine_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "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": { + "routine_documents_company_routine_key_uq": { + "name": "routine_documents_company_routine_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_documents_document_uq": { + "name": "routine_documents_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_documents_company_routine_updated_idx": { + "name": "routine_documents_company_routine_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_documents_company_id_companies_id_fk": { + "name": "routine_documents_company_id_companies_id_fk", + "tableFrom": "routine_documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "routine_documents_routine_id_routines_id_fk": { + "name": "routine_documents_routine_id_routines_id_fk", + "tableFrom": "routine_documents", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_documents_document_id_documents_id_fk": { + "name": "routine_documents_document_id_documents_id_fk", + "tableFrom": "routine_documents", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_revisions": { + "name": "routine_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision_number": { + "name": "revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "snapshot": { + "name": "snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "change_summary": { + "name": "change_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "restored_from_revision_id": { + "name": "restored_from_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routine_revisions_routine_revision_uq": { + "name": "routine_revisions_routine_revision_uq", + "columns": [ + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_revisions_company_routine_created_idx": { + "name": "routine_revisions_company_routine_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_revisions_company_responsible_user_idx": { + "name": "routine_revisions_company_responsible_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "responsible_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_revisions_company_id_companies_id_fk": { + "name": "routine_revisions_company_id_companies_id_fk", + "tableFrom": "routine_revisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_revisions_routine_id_routines_id_fk": { + "name": "routine_revisions_routine_id_routines_id_fk", + "tableFrom": "routine_revisions", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_revisions_restored_from_revision_id_routine_revisions_id_fk": { + "name": "routine_revisions_restored_from_revision_id_routine_revisions_id_fk", + "tableFrom": "routine_revisions", + "tableTo": "routine_revisions", + "columnsFrom": [ + "restored_from_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_revisions_created_by_agent_id_agents_id_fk": { + "name": "routine_revisions_created_by_agent_id_agents_id_fk", + "tableFrom": "routine_revisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_revisions_created_by_run_id_heartbeat_runs_id_fk": { + "name": "routine_revisions_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "routine_revisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_runs": { + "name": "routine_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trigger_id": { + "name": "trigger_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "triggered_at": { + "name": "triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "routine_revision_id": { + "name": "routine_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trigger_payload": { + "name": "trigger_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "dispatch_fingerprint": { + "name": "dispatch_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "linked_issue_id": { + "name": "linked_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "coalesced_into_run_id": { + "name": "coalesced_into_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "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": { + "routine_runs_company_routine_idx": { + "name": "routine_runs_company_routine_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_revision_idx": { + "name": "routine_runs_revision_idx", + "columns": [ + { + "expression": "routine_revision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_company_responsible_user_idx": { + "name": "routine_runs_company_responsible_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "responsible_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_trigger_idx": { + "name": "routine_runs_trigger_idx", + "columns": [ + { + "expression": "trigger_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_dispatch_fingerprint_idx": { + "name": "routine_runs_dispatch_fingerprint_idx", + "columns": [ + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "dispatch_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_linked_issue_idx": { + "name": "routine_runs_linked_issue_idx", + "columns": [ + { + "expression": "linked_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_trigger_idempotency_idx": { + "name": "routine_runs_trigger_idempotency_idx", + "columns": [ + { + "expression": "trigger_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_runs_company_id_companies_id_fk": { + "name": "routine_runs_company_id_companies_id_fk", + "tableFrom": "routine_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_runs_routine_id_routines_id_fk": { + "name": "routine_runs_routine_id_routines_id_fk", + "tableFrom": "routine_runs", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_runs_trigger_id_routine_triggers_id_fk": { + "name": "routine_runs_trigger_id_routine_triggers_id_fk", + "tableFrom": "routine_runs", + "tableTo": "routine_triggers", + "columnsFrom": [ + "trigger_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_runs_routine_revision_id_routine_revisions_id_fk": { + "name": "routine_runs_routine_revision_id_routine_revisions_id_fk", + "tableFrom": "routine_runs", + "tableTo": "routine_revisions", + "columnsFrom": [ + "routine_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_runs_linked_issue_id_issues_id_fk": { + "name": "routine_runs_linked_issue_id_issues_id_fk", + "tableFrom": "routine_runs", + "tableTo": "issues", + "columnsFrom": [ + "linked_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_triggers": { + "name": "routine_triggers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_fired_at": { + "name": "last_fired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "public_id": { + "name": "public_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "signing_mode": { + "name": "signing_mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "replay_window_sec": { + "name": "replay_window_sec", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_rotated_at": { + "name": "last_rotated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_result": { + "name": "last_result", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routine_triggers_company_routine_idx": { + "name": "routine_triggers_company_routine_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_company_kind_idx": { + "name": "routine_triggers_company_kind_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_next_run_idx": { + "name": "routine_triggers_next_run_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_public_id_idx": { + "name": "routine_triggers_public_id_idx", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_public_id_uq": { + "name": "routine_triggers_public_id_uq", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_triggers_company_id_companies_id_fk": { + "name": "routine_triggers_company_id_companies_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_triggers_routine_id_routines_id_fk": { + "name": "routine_triggers_routine_id_routines_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_triggers_secret_id_company_secrets_id_fk": { + "name": "routine_triggers_secret_id_company_secrets_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_triggers_created_by_agent_id_agents_id_fk": { + "name": "routine_triggers_created_by_agent_id_agents_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_triggers_updated_by_agent_id_agents_id_fk": { + "name": "routine_triggers_updated_by_agent_id_agents_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routines": { + "name": "routines", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "folder_id": { + "name": "folder_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_issue_id": { + "name": "parent_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_agent_id": { + "name": "assignee_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "concurrency_policy": { + "name": "concurrency_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'coalesce_if_active'" + }, + "catch_up_policy": { + "name": "catch_up_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'skip_missed'" + }, + "activity_gate_policy": { + "name": "activity_gate_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'always'" + }, + "activity_gate_scope": { + "name": "activity_gate_scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company'" + }, + "origin_kind": { + "name": "origin_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "origin_id": { + "name": "origin_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "variables": { + "name": "variables", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "env": { + "name": "env", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "latest_revision_id": { + "name": "latest_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "latest_revision_number": { + "name": "latest_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_triggered_at": { + "name": "last_triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_enqueued_at": { + "name": "last_enqueued_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": { + "routines_company_status_idx": { + "name": "routines_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_assignee_idx": { + "name": "routines_company_assignee_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_project_idx": { + "name": "routines_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_folder_idx": { + "name": "routines_company_folder_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_responsible_user_idx": { + "name": "routines_company_responsible_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "responsible_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_origin_idx": { + "name": "routines_company_origin_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routines_company_id_companies_id_fk": { + "name": "routines_company_id_companies_id_fk", + "tableFrom": "routines", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routines_project_id_projects_id_fk": { + "name": "routines_project_id_projects_id_fk", + "tableFrom": "routines", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routines_folder_id_folders_id_fk": { + "name": "routines_folder_id_folders_id_fk", + "tableFrom": "routines", + "tableTo": "folders", + "columnsFrom": [ + "folder_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_goal_id_goals_id_fk": { + "name": "routines_goal_id_goals_id_fk", + "tableFrom": "routines", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_parent_issue_id_issues_id_fk": { + "name": "routines_parent_issue_id_issues_id_fk", + "tableFrom": "routines", + "tableTo": "issues", + "columnsFrom": [ + "parent_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_assignee_agent_id_agents_id_fk": { + "name": "routines_assignee_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "assignee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "routines_created_by_agent_id_agents_id_fk": { + "name": "routines_created_by_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_updated_by_agent_id_agents_id_fk": { + "name": "routines_updated_by_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.run_identity_contexts": { + "name": "run_identity_contexts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "message_id": { + "name": "message_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_context_id": { + "name": "parent_context_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cause": { + "name": "cause", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'accepted'" + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "github": { + "name": "github", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "run_identity_contexts_run_revision_idx": { + "name": "run_identity_contexts_run_revision_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "run_identity_contexts_run_correlation_idx": { + "name": "run_identity_contexts_run_correlation_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "correlation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "run_identity_contexts_company_run_idx": { + "name": "run_identity_contexts_company_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "run_identity_contexts_company_id_companies_id_fk": { + "name": "run_identity_contexts_company_id_companies_id_fk", + "tableFrom": "run_identity_contexts", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.secret_access_events": { + "name": "secret_access_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_secret_definition_id": { + "name": "user_secret_definition_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "secret_scope": { + "name": "secret_scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company'" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_owner_user_id": { + "name": "credential_owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_subject_type": { + "name": "credential_subject_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_subject_id": { + "name": "credential_subject_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "consumer_type": { + "name": "consumer_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "consumer_id": { + "name": "consumer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config_path": { + "name": "config_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "secret_access_events_company_created_idx": { + "name": "secret_access_events_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secret_access_events_secret_created_idx": { + "name": "secret_access_events_secret_created_idx", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secret_access_events_user_definition_created_idx": { + "name": "secret_access_events_user_definition_created_idx", + "columns": [ + { + "expression": "user_secret_definition_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secret_access_events_company_credential_owner_idx": { + "name": "secret_access_events_company_credential_owner_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "credential_owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secret_access_events_consumer_idx": { + "name": "secret_access_events_consumer_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "consumer_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "consumer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secret_access_events_run_idx": { + "name": "secret_access_events_run_idx", + "columns": [ + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "secret_access_events_company_id_companies_id_fk": { + "name": "secret_access_events_company_id_companies_id_fk", + "tableFrom": "secret_access_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "secret_access_events_secret_id_company_secrets_id_fk": { + "name": "secret_access_events_secret_id_company_secrets_id_fk", + "tableFrom": "secret_access_events", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "secret_access_events_user_secret_definition_id_user_secret_definitions_id_fk": { + "name": "secret_access_events_user_secret_definition_id_user_secret_definitions_id_fk", + "tableFrom": "secret_access_events", + "tableTo": "user_secret_definitions", + "columnsFrom": [ + "user_secret_definition_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "secret_access_events_issue_id_issues_id_fk": { + "name": "secret_access_events_issue_id_issues_id_fk", + "tableFrom": "secret_access_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "secret_access_events_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "secret_access_events_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "secret_access_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "secret_access_events_plugin_id_plugins_id_fk": { + "name": "secret_access_events_plugin_id_plugins_id_fk", + "tableFrom": "secret_access_events", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.smoke_run_steps": { + "name": "smoke_run_steps", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scenario_step": { + "name": "scenario_step", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "detail": { + "name": "detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "screenshot_artifact_ref": { + "name": "screenshot_artifact_ref", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "smoke_run_steps_company_run_idx": { + "name": "smoke_run_steps_company_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "smoke_run_steps_company_path_idx": { + "name": "smoke_run_steps_company_path_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "smoke_run_steps_company_id_companies_id_fk": { + "name": "smoke_run_steps_company_id_companies_id_fk", + "tableFrom": "smoke_run_steps", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "smoke_run_steps_run_id_smoke_runs_id_fk": { + "name": "smoke_run_steps_run_id_smoke_runs_id_fk", + "tableFrom": "smoke_run_steps", + "tableTo": "smoke_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.smoke_runs": { + "name": "smoke_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_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": "'running'" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "summary": { + "name": "summary", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "smoke_runs_company_started_idx": { + "name": "smoke_runs_company_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "smoke_runs_company_status_idx": { + "name": "smoke_runs_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "smoke_runs_company_id_companies_id_fk": { + "name": "smoke_runs_company_id_companies_id_fk", + "tableFrom": "smoke_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.status_card_updates": { + "name": "status_card_updates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "card_id": { + "name": "card_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "generation_issue_id": { + "name": "generation_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "changes": { + "name": "changes", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "input_tokens": { + "name": "input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "output_tokens": { + "name": "output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cost_cents": { + "name": "cost_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "query_version": { + "name": "query_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "change_summary": { + "name": "change_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "status_card_updates_card_started_idx": { + "name": "status_card_updates_card_started_idx", + "columns": [ + { + "expression": "card_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "status_card_updates_generation_issue_idx": { + "name": "status_card_updates_generation_issue_idx", + "columns": [ + { + "expression": "generation_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "status_card_updates_card_id_status_cards_id_fk": { + "name": "status_card_updates_card_id_status_cards_id_fk", + "tableFrom": "status_card_updates", + "tableTo": "status_cards", + "columnsFrom": [ + "card_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "status_card_updates_generation_issue_id_issues_id_fk": { + "name": "status_card_updates_generation_issue_id_issues_id_fk", + "tableFrom": "status_card_updates", + "tableTo": "issues", + "columnsFrom": [ + "generation_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "status_card_updates_run_id_heartbeat_runs_id_fk": { + "name": "status_card_updates_run_id_heartbeat_runs_id_fk", + "tableFrom": "status_card_updates", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.status_cards": { + "name": "status_cards", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title_pinned": { + "name": "title_pinned", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "interest_prompt": { + "name": "interest_prompt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "queries": { + "name": "queries", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "query_version": { + "name": "query_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "query_compiled_at": { + "name": "query_compiled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "query_compiled_by_agent_id": { + "name": "query_compiled_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "refresh_policy": { + "name": "refresh_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'compiling'" + }, + "pending_change_count": { + "name": "pending_change_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "pending_change_hash": { + "name": "pending_change_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_change_at": { + "name": "last_change_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "fingerprint": { + "name": "fingerprint", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "fingerprint_at": { + "name": "fingerprint_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "mentioned_issue_ids": { + "name": "mentioned_issue_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_update_run_kind": { + "name": "last_update_run_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_generated_at": { + "name": "last_generated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_model": { + "name": "last_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "generating_issue_id": { + "name": "generating_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_eval_at": { + "name": "next_eval_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "archived_by_user_id": { + "name": "archived_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_by_agent_id": { + "name": "archived_by_agent_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": { + "status_cards_company_archived_idx": { + "name": "status_cards_company_archived_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "status_cards_company_next_eval_idx": { + "name": "status_cards_company_next_eval_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_eval_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "status_cards_company_id_companies_id_fk": { + "name": "status_cards_company_id_companies_id_fk", + "tableFrom": "status_cards", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "status_cards_created_by_agent_id_agents_id_fk": { + "name": "status_cards_created_by_agent_id_agents_id_fk", + "tableFrom": "status_cards", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "status_cards_query_compiled_by_agent_id_agents_id_fk": { + "name": "status_cards_query_compiled_by_agent_id_agents_id_fk", + "tableFrom": "status_cards", + "tableTo": "agents", + "columnsFrom": [ + "query_compiled_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "status_cards_agent_id_agents_id_fk": { + "name": "status_cards_agent_id_agents_id_fk", + "tableFrom": "status_cards", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "status_cards_document_id_documents_id_fk": { + "name": "status_cards_document_id_documents_id_fk", + "tableFrom": "status_cards", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "status_cards_generating_issue_id_issues_id_fk": { + "name": "status_cards_generating_issue_id_issues_id_fk", + "tableFrom": "status_cards", + "tableTo": "issues", + "columnsFrom": [ + "generating_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "status_cards_archived_by_agent_id_agents_id_fk": { + "name": "status_cards_archived_by_agent_id_agents_id_fk", + "tableFrom": "status_cards", + "tableTo": "agents", + "columnsFrom": [ + "archived_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.status_decision_effects": { + "name": "status_decision_effects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "decision_id": { + "name": "decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "ordinal": { + "name": "ordinal", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "effect_kind": { + "name": "effect_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "delivery_state": { + "name": "delivery_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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": { + "status_decision_effects_decision_ordinal_uq": { + "name": "status_decision_effects_decision_ordinal_uq", + "columns": [ + { + "expression": "decision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "ordinal", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "status_decision_effects_company_idempotency_uq": { + "name": "status_decision_effects_company_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "status_decision_effects_company_id_companies_id_fk": { + "name": "status_decision_effects_company_id_companies_id_fk", + "tableFrom": "status_decision_effects", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "status_decision_effects_issue_company_fk": { + "name": "status_decision_effects_issue_company_fk", + "tableFrom": "status_decision_effects", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "status_decision_effects_decision_owner_fk": { + "name": "status_decision_effects_decision_owner_fk", + "tableFrom": "status_decision_effects", + "tableTo": "status_decisions", + "columnsFrom": [ + "company_id", + "issue_id", + "decision_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.status_decisions": { + "name": "status_decisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "assessment_id": { + "name": "assessment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "decision_version": { + "name": "decision_version", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "policy_version": { + "name": "policy_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_status": { + "name": "from_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "to_status": { + "name": "to_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason_code": { + "name": "reason_code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "decision_json": { + "name": "decision_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "decision_digest": { + "name": "decision_digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "application_state": { + "name": "application_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'proposed'" + }, + "supersedes_decision_id": { + "name": "supersedes_decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "applied_at": { + "name": "applied_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": { + "status_decisions_company_issue_version_uq": { + "name": "status_decisions_company_issue_version_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "decision_version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "status_decisions_company_assessment_uq": { + "name": "status_decisions_company_assessment_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assessment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "status_decisions_company_issue_digest_uq": { + "name": "status_decisions_company_issue_digest_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "decision_digest", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "status_decisions_company_id_companies_id_fk": { + "name": "status_decisions_company_id_companies_id_fk", + "tableFrom": "status_decisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "status_decisions_issue_company_fk": { + "name": "status_decisions_issue_company_fk", + "tableFrom": "status_decisions", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "status_decisions_assessment_owner_fk": { + "name": "status_decisions_assessment_owner_fk", + "tableFrom": "status_decisions", + "tableTo": "work_assessments", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "assessment_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "run_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "status_decisions_supersedes_owner_fk": { + "name": "status_decisions_supersedes_owner_fk", + "tableFrom": "status_decisions", + "tableTo": "status_decisions", + "columnsFrom": [ + "company_id", + "issue_id", + "supersedes_decision_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "status_decisions_company_issue_id_uq": { + "name": "status_decisions_company_issue_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "issue_id", + "id" + ] + }, + "status_decisions_company_issue_run_assessment_id_uq": { + "name": "status_decisions_company_issue_run_assessment_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "issue_id", + "run_id", + "assessment_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.summary_slots": { + "name": "summary_slots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_kind": { + "name": "scope_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "slot_key": { + "name": "slot_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "generating_issue_id": { + "name": "generating_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_generated_at": { + "name": "last_generated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_generated_by_agent_id": { + "name": "last_generated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_model": { + "name": "last_model", + "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": { + "summary_slots_document_uq": { + "name": "summary_slots_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "summary_slots_company_scope_idx": { + "name": "summary_slots_company_scope_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "summary_slots_company_generating_issue_idx": { + "name": "summary_slots_company_generating_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "generating_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "summary_slots_company_updated_idx": { + "name": "summary_slots_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "summary_slots_company_id_companies_id_fk": { + "name": "summary_slots_company_id_companies_id_fk", + "tableFrom": "summary_slots", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "summary_slots_document_id_documents_id_fk": { + "name": "summary_slots_document_id_documents_id_fk", + "tableFrom": "summary_slots", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "summary_slots_generating_issue_id_issues_id_fk": { + "name": "summary_slots_generating_issue_id_issues_id_fk", + "tableFrom": "summary_slots", + "tableTo": "issues", + "columnsFrom": [ + "generating_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "summary_slots_last_generated_by_agent_id_agents_id_fk": { + "name": "summary_slots_last_generated_by_agent_id_agents_id_fk", + "tableFrom": "summary_slots", + "tableTo": "agents", + "columnsFrom": [ + "last_generated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "summary_slots_company_scope_slot_uq": { + "name": "summary_slots_company_scope_slot_uq", + "nullsNotDistinct": true, + "columns": [ + "company_id", + "scope_kind", + "scope_id", + "slot_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_access_audit_events": { + "name": "tool_access_audit_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "gateway_id": { + "name": "gateway_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_token_id": { + "name": "gateway_token_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_public_id": { + "name": "gateway_public_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "catalog_entry_id": { + "name": "catalog_entry_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason_code": { + "name": "reason_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "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": { + "tool_access_audit_company_created_idx": { + "name": "tool_access_audit_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_access_audit_connection_idx": { + "name": "tool_access_audit_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_access_audit_gateway_idx": { + "name": "tool_access_audit_gateway_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "gateway_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_access_audit_events_company_id_companies_id_fk": { + "name": "tool_access_audit_events_company_id_companies_id_fk", + "tableFrom": "tool_access_audit_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_access_audit_events_gateway_id_tool_mcp_gateways_id_fk": { + "name": "tool_access_audit_events_gateway_id_tool_mcp_gateways_id_fk", + "tableFrom": "tool_access_audit_events", + "tableTo": "tool_mcp_gateways", + "columnsFrom": [ + "gateway_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_access_audit_events_gateway_token_id_tool_mcp_gateway_tokens_id_fk": { + "name": "tool_access_audit_events_gateway_token_id_tool_mcp_gateway_tokens_id_fk", + "tableFrom": "tool_access_audit_events", + "tableTo": "tool_mcp_gateway_tokens", + "columnsFrom": [ + "gateway_token_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_access_audit_events_connection_id_tool_connections_id_fk": { + "name": "tool_access_audit_events_connection_id_tool_connections_id_fk", + "tableFrom": "tool_access_audit_events", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_access_audit_events_catalog_entry_id_tool_catalog_entries_id_fk": { + "name": "tool_access_audit_events_catalog_entry_id_tool_catalog_entries_id_fk", + "tableFrom": "tool_access_audit_events", + "tableTo": "tool_catalog_entries", + "columnsFrom": [ + "catalog_entry_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_action_deliveries": { + "name": "tool_action_deliveries", + "schema": "", + "columns": { + "action_request_id": { + "name": "action_request_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "interaction_id": { + "name": "interaction_id", + "type": "uuid", + "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()" + } + }, + "indexes": { + "tool_action_deliveries_pending_idx": { + "name": "tool_action_deliveries_pending_idx", + "columns": [ + { + "expression": "delivered_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_action_deliveries_action_request_id_tool_action_requests_id_fk": { + "name": "tool_action_deliveries_action_request_id_tool_action_requests_id_fk", + "tableFrom": "tool_action_deliveries", + "tableTo": "tool_action_requests", + "columnsFrom": [ + "action_request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_action_deliveries_company_id_companies_id_fk": { + "name": "tool_action_deliveries_company_id_companies_id_fk", + "tableFrom": "tool_action_deliveries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_action_deliveries_issue_id_issues_id_fk": { + "name": "tool_action_deliveries_issue_id_issues_id_fk", + "tableFrom": "tool_action_deliveries", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_action_deliveries_interaction_id_issue_thread_interactions_id_fk": { + "name": "tool_action_deliveries_interaction_id_issue_thread_interactions_id_fk", + "tableFrom": "tool_action_deliveries", + "tableTo": "issue_thread_interactions", + "columnsFrom": [ + "interaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_action_requests": { + "name": "tool_action_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "invocation_id": { + "name": "invocation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "interaction_id": { + "name": "interaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "canonical_arguments_hash": { + "name": "canonical_arguments_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "canonical_arguments_summary": { + "name": "canonical_arguments_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "signed_arguments": { + "name": "signed_arguments", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "preview_markdown": { + "name": "preview_markdown", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_by_agent_id": { + "name": "requested_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_by_user_id": { + "name": "requested_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_by_agent_id": { + "name": "resolved_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_by_user_id": { + "name": "resolved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_by_agent_id": { + "name": "decided_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "decided_by_user_id": { + "name": "decided_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_at": { + "name": "decided_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_action_requests_company_status_idx": { + "name": "tool_action_requests_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_action_requests_invocation_idx": { + "name": "tool_action_requests_invocation_idx", + "columns": [ + { + "expression": "invocation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_action_requests_issue_idx": { + "name": "tool_action_requests_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_action_requests_company_id_companies_id_fk": { + "name": "tool_action_requests_company_id_companies_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_action_requests_invocation_id_tool_invocations_id_fk": { + "name": "tool_action_requests_invocation_id_tool_invocations_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "tool_invocations", + "columnsFrom": [ + "invocation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_action_requests_issue_id_issues_id_fk": { + "name": "tool_action_requests_issue_id_issues_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_action_requests_interaction_id_issue_thread_interactions_id_fk": { + "name": "tool_action_requests_interaction_id_issue_thread_interactions_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "issue_thread_interactions", + "columnsFrom": [ + "interaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_action_requests_approval_id_approvals_id_fk": { + "name": "tool_action_requests_approval_id_approvals_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_action_requests_requested_by_agent_id_agents_id_fk": { + "name": "tool_action_requests_requested_by_agent_id_agents_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "agents", + "columnsFrom": [ + "requested_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_action_requests_resolved_by_agent_id_agents_id_fk": { + "name": "tool_action_requests_resolved_by_agent_id_agents_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "agents", + "columnsFrom": [ + "resolved_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_action_requests_decided_by_agent_id_agents_id_fk": { + "name": "tool_action_requests_decided_by_agent_id_agents_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "agents", + "columnsFrom": [ + "decided_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_applications": { + "name": "tool_applications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "application_key": { + "name": "application_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "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": { + "tool_applications_company_idx": { + "name": "tool_applications_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_applications_company_status_idx": { + "name": "tool_applications_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_applications_company_name_uq": { + "name": "tool_applications_company_name_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_applications_company_key_uq": { + "name": "tool_applications_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "application_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_applications_company_id_companies_id_fk": { + "name": "tool_applications_company_id_companies_id_fk", + "tableFrom": "tool_applications", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_applications_plugin_id_plugins_id_fk": { + "name": "tool_applications_plugin_id_plugins_id_fk", + "tableFrom": "tool_applications", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_applications_owner_agent_id_agents_id_fk": { + "name": "tool_applications_owner_agent_id_agents_id_fk", + "tableFrom": "tool_applications", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_call_events": { + "name": "tool_call_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_id": { + "name": "gateway_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_token_id": { + "name": "gateway_token_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_public_id": { + "name": "gateway_public_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_subject_type": { + "name": "client_subject_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_subject_id": { + "name": "client_subject_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mcp_session_id": { + "name": "mcp_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "catalog_entry_id": { + "name": "catalog_entry_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "invocation_id": { + "name": "invocation_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "action_request_id": { + "name": "action_request_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "runtime_slot_id": { + "name": "runtime_slot_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decision": { + "name": "decision", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "matched_policy_ids": { + "name": "matched_policy_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "reason_code": { + "name": "reason_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "policy_explanation": { + "name": "policy_explanation", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "credential_scope_summary": { + "name": "credential_scope_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "header_policy_summary": { + "name": "header_policy_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "latency_ms": { + "name": "latency_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "arguments_summary": { + "name": "arguments_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "request_hash": { + "name": "request_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_summary": { + "name": "request_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "result_hash": { + "name": "result_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_summary": { + "name": "result_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "result_size_bytes": { + "name": "result_size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "redaction_plan": { + "name": "redaction_plan", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "rate_limit_state": { + "name": "rate_limit_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_call_events_company_created_idx": { + "name": "tool_call_events_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_call_events_run_idx": { + "name": "tool_call_events_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_call_events_issue_idx": { + "name": "tool_call_events_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_call_events_invocation_idx": { + "name": "tool_call_events_invocation_idx", + "columns": [ + { + "expression": "invocation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_call_events_gateway_idx": { + "name": "tool_call_events_gateway_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "gateway_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_call_events_company_id_companies_id_fk": { + "name": "tool_call_events_company_id_companies_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_call_events_agent_id_agents_id_fk": { + "name": "tool_call_events_agent_id_agents_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_run_id_heartbeat_runs_id_fk": { + "name": "tool_call_events_run_id_heartbeat_runs_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_issue_id_issues_id_fk": { + "name": "tool_call_events_issue_id_issues_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_gateway_id_tool_mcp_gateways_id_fk": { + "name": "tool_call_events_gateway_id_tool_mcp_gateways_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_mcp_gateways", + "columnsFrom": [ + "gateway_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_gateway_token_id_tool_mcp_gateway_tokens_id_fk": { + "name": "tool_call_events_gateway_token_id_tool_mcp_gateway_tokens_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_mcp_gateway_tokens", + "columnsFrom": [ + "gateway_token_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_application_id_tool_applications_id_fk": { + "name": "tool_call_events_application_id_tool_applications_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_connection_id_tool_connections_id_fk": { + "name": "tool_call_events_connection_id_tool_connections_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_catalog_entry_id_tool_catalog_entries_id_fk": { + "name": "tool_call_events_catalog_entry_id_tool_catalog_entries_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_catalog_entries", + "columnsFrom": [ + "catalog_entry_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_invocation_id_tool_invocations_id_fk": { + "name": "tool_call_events_invocation_id_tool_invocations_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_invocations", + "columnsFrom": [ + "invocation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_action_request_id_tool_action_requests_id_fk": { + "name": "tool_call_events_action_request_id_tool_action_requests_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_action_requests", + "columnsFrom": [ + "action_request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_runtime_slot_id_tool_runtime_slots_id_fk": { + "name": "tool_call_events_runtime_slot_id_tool_runtime_slots_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_runtime_slots", + "columnsFrom": [ + "runtime_slot_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_catalog_entries": { + "name": "tool_catalog_entries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "entry_kind": { + "name": "entry_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'tool'" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "input_schema": { + "name": "input_schema", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "output_schema": { + "name": "output_schema", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "annotations": { + "name": "annotations", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "risk_level": { + "name": "risk_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'read'" + }, + "is_read_only": { + "name": "is_read_only", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_write": { + "name": "is_write", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_destructive": { + "name": "is_destructive", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "version_hash": { + "name": "version_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schema_hash": { + "name": "schema_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "first_seen_at": { + "name": "first_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "reviewed_at": { + "name": "reviewed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "reviewed_by_agent_id": { + "name": "reviewed_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "reviewed_by_user_id": { + "name": "reviewed_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "quarantined_at": { + "name": "quarantined_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "quarantine_reason": { + "name": "quarantine_reason", + "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": { + "tool_catalog_entries_company_idx": { + "name": "tool_catalog_entries_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_catalog_entries_application_idx": { + "name": "tool_catalog_entries_application_idx", + "columns": [ + { + "expression": "application_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_catalog_entries_connection_idx": { + "name": "tool_catalog_entries_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_catalog_entries_company_status_idx": { + "name": "tool_catalog_entries_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_catalog_entries_connection_name_uq": { + "name": "tool_catalog_entries_connection_name_uq", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_catalog_entries_company_id_companies_id_fk": { + "name": "tool_catalog_entries_company_id_companies_id_fk", + "tableFrom": "tool_catalog_entries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_catalog_entries_application_id_tool_applications_id_fk": { + "name": "tool_catalog_entries_application_id_tool_applications_id_fk", + "tableFrom": "tool_catalog_entries", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_catalog_entries_connection_id_tool_connections_id_fk": { + "name": "tool_catalog_entries_connection_id_tool_connections_id_fk", + "tableFrom": "tool_catalog_entries", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_catalog_entries_reviewed_by_agent_id_agents_id_fk": { + "name": "tool_catalog_entries_reviewed_by_agent_id_agents_id_fk", + "tableFrom": "tool_catalog_entries", + "tableTo": "agents", + "columnsFrom": [ + "reviewed_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_connection_installs": { + "name": "tool_connection_installs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "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 + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_connection_installs_company_target_idx": { + "name": "tool_connection_installs_company_target_idx", + "columns": [ + { + "expression": "company_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": {} + }, + "tool_connection_installs_connection_idx": { + "name": "tool_connection_installs_connection_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_connection_installs_target_uq": { + "name": "tool_connection_installs_target_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_connection_installs_company_id_companies_id_fk": { + "name": "tool_connection_installs_company_id_companies_id_fk", + "tableFrom": "tool_connection_installs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_connection_installs_connection_id_tool_connections_id_fk": { + "name": "tool_connection_installs_connection_id_tool_connections_id_fk", + "tableFrom": "tool_connection_installs", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_connection_installs_created_by_agent_id_agents_id_fk": { + "name": "tool_connection_installs_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_connection_installs", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "tool_connection_installs_target_type_check": { + "name": "tool_connection_installs_target_type_check", + "value": "\"tool_connection_installs\".\"target_type\" in ('company', 'agent')" + } + }, + "isRLSEnabled": false + }, + "public.tool_connections": { + "name": "tool_connections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "uid": { + "name": "uid", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connection_kind": { + "name": "connection_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'managed'" + }, + "connection_purpose": { + "name": "connection_purpose", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'tool'" + }, + "ownership": { + "name": "ownership", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'customer'" + }, + "transport": { + "name": "transport", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "auth_kind": { + "name": "auth_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "credential_source": { + "name": "credential_source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip_vault'" + }, + "external_credential": { + "name": "external_credential", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "credential_policy": { + "name": "credential_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'shared'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "transport_config": { + "name": "transport_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "credential_refs": { + "name": "credential_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "credential_secret_refs": { + "name": "credential_secret_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unchecked'" + }, + "health_message": { + "name": "health_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "health_checked_at": { + "name": "health_checked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_health_at": { + "name": "last_health_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_catalog_refresh_at": { + "name": "last_catalog_refresh_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_connections_company_idx": { + "name": "tool_connections_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_connections_application_idx": { + "name": "tool_connections_application_idx", + "columns": [ + { + "expression": "application_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_connections_company_enabled_idx": { + "name": "tool_connections_company_enabled_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_connections_company_uid_uq": { + "name": "tool_connections_company_uid_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "uid", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_connections_company_id_companies_id_fk": { + "name": "tool_connections_company_id_companies_id_fk", + "tableFrom": "tool_connections", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_connections_application_id_tool_applications_id_fk": { + "name": "tool_connections_application_id_tool_applications_id_fk", + "tableFrom": "tool_connections", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tool_connections_created_by_agent_id_agents_id_fk": { + "name": "tool_connections_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_connections", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "tool_connections_company_id_uq": { + "name": "tool_connections_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "tool_connections_ownership_check": { + "name": "tool_connections_ownership_check", + "value": "\"tool_connections\".\"ownership\" in ('platform_shared', 'platform_provisioned', 'customer', 'dcr')" + }, + "tool_connections_transport_check": { + "name": "tool_connections_transport_check", + "value": "\"tool_connections\".\"transport\" in ('mcp_remote', 'rest_api', 'local_stdio', 'chat_sdk', 'runtime_auth')" + }, + "tool_connections_purpose_check": { + "name": "tool_connections_purpose_check", + "value": "\"tool_connections\".\"connection_purpose\" in ('tool', 'channel', 'ai')" + }, + "tool_connections_channel_transport_check": { + "name": "tool_connections_channel_transport_check", + "value": "(\n (\"tool_connections\".\"connection_purpose\" = 'tool' and \"tool_connections\".\"transport\" not in ('chat_sdk', 'runtime_auth'))\n or\n (\"tool_connections\".\"connection_purpose\" = 'channel' and (\"tool_connections\".\"transport\" = 'chat_sdk' or (\"tool_connections\".\"transport\" = 'rest_api' and \"tool_connections\".\"config\"->>'provider' = 'agentmail')))\n or\n (\"tool_connections\".\"connection_purpose\" = 'ai' and \"tool_connections\".\"transport\" = 'runtime_auth')\n )" + }, + "tool_connections_auth_kind_check": { + "name": "tool_connections_auth_kind_check", + "value": "\"tool_connections\".\"auth_kind\" in ('oauth', 'api_key', 'none')" + }, + "tool_connections_credential_source_check": { + "name": "tool_connections_credential_source_check", + "value": "\"tool_connections\".\"credential_source\" in ('paperclip_vault', 'vercel_connect')" + }, + "tool_connections_credential_source_one_of_check": { + "name": "tool_connections_credential_source_one_of_check", + "value": "(\n (\"tool_connections\".\"credential_source\" = 'paperclip_vault' and \"tool_connections\".\"external_credential\" is null)\n or\n (\"tool_connections\".\"credential_source\" = 'vercel_connect' and \"tool_connections\".\"external_credential\" is not null and jsonb_array_length(\"tool_connections\".\"credential_refs\") = 0 and jsonb_array_length(\"tool_connections\".\"credential_secret_refs\") = 0)\n )" + }, + "tool_connections_credential_policy_check": { + "name": "tool_connections_credential_policy_check", + "value": "\"tool_connections\".\"credential_policy\" in ('shared', 'per_user', 'per_user_with_fallback', 'per_agent')" + } + }, + "isRLSEnabled": false + }, + "public.tool_gateway_rate_limit_counters": { + "name": "tool_gateway_rate_limit_counters", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "counter_key": { + "name": "counter_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_start_at": { + "name": "window_start_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "window_ms": { + "name": "window_ms", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "limit": { + "name": "limit", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "reset_at": { + "name": "reset_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": { + "tool_gateway_rate_limit_counters_company_idx": { + "name": "tool_gateway_rate_limit_counters_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_gateway_rate_limit_counters_window_uq": { + "name": "tool_gateway_rate_limit_counters_window_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "counter_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_start_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_gateway_rate_limit_counters_company_id_companies_id_fk": { + "name": "tool_gateway_rate_limit_counters_company_id_companies_id_fk", + "tableFrom": "tool_gateway_rate_limit_counters", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_gateway_sessions": { + "name": "tool_gateway_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_id": { + "name": "gateway_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_token_id": { + "name": "gateway_token_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_public_id": { + "name": "gateway_public_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_subject_type": { + "name": "client_subject_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_subject_id": { + "name": "client_subject_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mcp_session_id": { + "name": "mcp_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "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 + }, + "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": { + "tool_gateway_sessions_token_hash_uq": { + "name": "tool_gateway_sessions_token_hash_uq", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_gateway_sessions_company_agent_idx": { + "name": "tool_gateway_sessions_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_gateway_sessions_company_expires_idx": { + "name": "tool_gateway_sessions_company_expires_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_gateway_sessions_run_idx": { + "name": "tool_gateway_sessions_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_gateway_sessions_issue_idx": { + "name": "tool_gateway_sessions_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_gateway_sessions_gateway_idx": { + "name": "tool_gateway_sessions_gateway_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "gateway_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_gateway_sessions_company_id_companies_id_fk": { + "name": "tool_gateway_sessions_company_id_companies_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_gateway_sessions_agent_id_agents_id_fk": { + "name": "tool_gateway_sessions_agent_id_agents_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_gateway_sessions_run_id_heartbeat_runs_id_fk": { + "name": "tool_gateway_sessions_run_id_heartbeat_runs_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_gateway_sessions_issue_id_issues_id_fk": { + "name": "tool_gateway_sessions_issue_id_issues_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_gateway_sessions_project_id_projects_id_fk": { + "name": "tool_gateway_sessions_project_id_projects_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_gateway_sessions_gateway_id_tool_mcp_gateways_id_fk": { + "name": "tool_gateway_sessions_gateway_id_tool_mcp_gateways_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "tool_mcp_gateways", + "columnsFrom": [ + "gateway_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_gateway_sessions_gateway_token_id_tool_mcp_gateway_tokens_id_fk": { + "name": "tool_gateway_sessions_gateway_token_id_tool_mcp_gateway_tokens_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "tool_mcp_gateway_tokens", + "columnsFrom": [ + "gateway_token_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_invocations": { + "name": "tool_invocations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_id": { + "name": "gateway_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_token_id": { + "name": "gateway_token_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_public_id": { + "name": "gateway_public_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_subject_type": { + "name": "client_subject_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_subject_id": { + "name": "client_subject_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mcp_session_id": { + "name": "mcp_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "catalog_entry_id": { + "name": "catalog_entry_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "catalog_version_hash": { + "name": "catalog_version_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "catalog_schema_hash": { + "name": "catalog_schema_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_type": { + "name": "provider_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "application_key": { + "name": "application_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "upstream_tool_name": { + "name": "upstream_tool_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "risk_level": { + "name": "risk_level", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "arguments_hash": { + "name": "arguments_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "arguments_summary": { + "name": "arguments_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "policy_decision": { + "name": "policy_decision", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "matched_policy_ids": { + "name": "matched_policy_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "policy_explanation": { + "name": "policy_explanation", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "credential_scope_summary": { + "name": "credential_scope_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "header_policy_summary": { + "name": "header_policy_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "approval_state": { + "name": "approval_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'not_required'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "upstream_request_id": { + "name": "upstream_request_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_hash": { + "name": "result_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_summary": { + "name": "result_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "result_size_bytes": { + "name": "result_size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "result_artifact_id": { + "name": "result_artifact_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "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": { + "tool_invocations_company_created_idx": { + "name": "tool_invocations_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_invocations_run_idx": { + "name": "tool_invocations_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_invocations_issue_idx": { + "name": "tool_invocations_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_invocations_gateway_idx": { + "name": "tool_invocations_gateway_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "gateway_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_invocations_company_idempotency_uq": { + "name": "tool_invocations_company_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_invocations_company_id_companies_id_fk": { + "name": "tool_invocations_company_id_companies_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_invocations_agent_id_agents_id_fk": { + "name": "tool_invocations_agent_id_agents_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_issue_id_issues_id_fk": { + "name": "tool_invocations_issue_id_issues_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_run_id_heartbeat_runs_id_fk": { + "name": "tool_invocations_run_id_heartbeat_runs_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_gateway_id_tool_mcp_gateways_id_fk": { + "name": "tool_invocations_gateway_id_tool_mcp_gateways_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "tool_mcp_gateways", + "columnsFrom": [ + "gateway_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_gateway_token_id_tool_mcp_gateway_tokens_id_fk": { + "name": "tool_invocations_gateway_token_id_tool_mcp_gateway_tokens_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "tool_mcp_gateway_tokens", + "columnsFrom": [ + "gateway_token_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_application_id_tool_applications_id_fk": { + "name": "tool_invocations_application_id_tool_applications_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_connection_id_tool_connections_id_fk": { + "name": "tool_invocations_connection_id_tool_connections_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_catalog_entry_id_tool_catalog_entries_id_fk": { + "name": "tool_invocations_catalog_entry_id_tool_catalog_entries_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "tool_catalog_entries", + "columnsFrom": [ + "catalog_entry_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_mcp_gateway_tokens": { + "name": "tool_mcp_gateway_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "gateway_id": { + "name": "gateway_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, + "default": "''" + }, + "subject_type": { + "name": "subject_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'gateway_client'" + }, + "subject_id": { + "name": "subject_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_label": { + "name": "client_label", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "owner_note": { + "name": "owner_note", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "allowed_actions": { + "name": "allowed_actions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[\"tools/list\",\"tools/call\"]'::jsonb" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expiry_override_reason": { + "name": "expiry_override_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expiry_override_by_user_id": { + "name": "expiry_override_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expiry_override_by_agent_id": { + "name": "expiry_override_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "expiry_override_at": { + "name": "expiry_override_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "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 + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_mcp_gateway_tokens_token_hash_uq": { + "name": "tool_mcp_gateway_tokens_token_hash_uq", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateway_tokens_gateway_idx": { + "name": "tool_mcp_gateway_tokens_gateway_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "gateway_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateway_tokens_subject_idx": { + "name": "tool_mcp_gateway_tokens_subject_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateway_tokens_company_expires_idx": { + "name": "tool_mcp_gateway_tokens_company_expires_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_mcp_gateway_tokens_company_id_companies_id_fk": { + "name": "tool_mcp_gateway_tokens_company_id_companies_id_fk", + "tableFrom": "tool_mcp_gateway_tokens", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_mcp_gateway_tokens_gateway_id_tool_mcp_gateways_id_fk": { + "name": "tool_mcp_gateway_tokens_gateway_id_tool_mcp_gateways_id_fk", + "tableFrom": "tool_mcp_gateway_tokens", + "tableTo": "tool_mcp_gateways", + "columnsFrom": [ + "gateway_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_mcp_gateway_tokens_expiry_override_by_agent_id_agents_id_fk": { + "name": "tool_mcp_gateway_tokens_expiry_override_by_agent_id_agents_id_fk", + "tableFrom": "tool_mcp_gateway_tokens", + "tableTo": "agents", + "columnsFrom": [ + "expiry_override_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_mcp_gateway_tokens_created_by_agent_id_agents_id_fk": { + "name": "tool_mcp_gateway_tokens_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_mcp_gateway_tokens", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_mcp_gateways": { + "name": "tool_mcp_gateways", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "gateway_public_id": { + "name": "gateway_public_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'gw_' || replace(gen_random_uuid()::text, '-', '')" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_slug": { + "name": "display_slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "default_profile_mode": { + "name": "default_profile_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'gateway_only'" + }, + "context_scope_type": { + "name": "context_scope_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "context_scope_id": { + "name": "context_scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approval_issue_id": { + "name": "approval_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "auth_config": { + "name": "auth_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"version\":1,\"bearer\":{\"enabled\":true,\"tokenPrefix\":\"pcgw\",\"defaultTtlSeconds\":7776000,\"requireFiniteExpiry\":true,\"longLivedTokenRequiresOverride\":true},\"oauth\":{\"enabled\":false,\"reservedFor\":\"v1_5\",\"dynamicClientRegistration\":false,\"authorizationCodePkce\":false}}'::jsonb" + }, + "header_policy": { + "name": "header_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"version\":1,\"callerPassthrough\":{\"enabled\":false,\"allowedHeaders\":[]},\"staticHeaders\":[],\"generatedMetadata\":{\"enabled\":false,\"allowedHeaders\":[]},\"responseHeaders\":{\"forwardMcpRequiredHeaders\":true,\"forwardSafeCacheHeaders\":true}}'::jsonb" + }, + "metadata_policy": { + "name": "metadata_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"version\":1,\"forwardCompanyId\":false,\"forwardGatewayId\":false,\"forwardProjectId\":false,\"forwardIssueId\":false,\"forwardAgentId\":false,\"forwardRunId\":false,\"forwardCorrelationId\":true}'::jsonb" + }, + "on_demand_tools_config": { + "name": "on_demand_tools_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"enabled\":false,\"searchToolName\":\"search_tools\",\"runToolName\":\"run_tool\"}'::jsonb" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "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": { + "tool_mcp_gateways_company_idx": { + "name": "tool_mcp_gateways_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateways_company_status_idx": { + "name": "tool_mcp_gateways_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateways_profile_idx": { + "name": "tool_mcp_gateways_profile_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateways_public_id_uq": { + "name": "tool_mcp_gateways_public_id_uq", + "columns": [ + { + "expression": "gateway_public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateways_company_slug_uq": { + "name": "tool_mcp_gateways_company_slug_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateways_company_name_uq": { + "name": "tool_mcp_gateways_company_name_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_mcp_gateways_company_id_companies_id_fk": { + "name": "tool_mcp_gateways_company_id_companies_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_mcp_gateways_profile_id_tool_profiles_id_fk": { + "name": "tool_mcp_gateways_profile_id_tool_profiles_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "tool_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "tool_mcp_gateways_agent_id_agents_id_fk": { + "name": "tool_mcp_gateways_agent_id_agents_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_mcp_gateways_project_id_projects_id_fk": { + "name": "tool_mcp_gateways_project_id_projects_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_mcp_gateways_issue_id_issues_id_fk": { + "name": "tool_mcp_gateways_issue_id_issues_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_mcp_gateways_approval_issue_id_issues_id_fk": { + "name": "tool_mcp_gateways_approval_issue_id_issues_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "issues", + "columnsFrom": [ + "approval_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_mcp_gateways_created_by_agent_id_agents_id_fk": { + "name": "tool_mcp_gateways_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_oauth_states": { + "name": "tool_oauth_states", + "schema": "", + "columns": { + "state": { + "name": "state", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "code_verifier": { + "name": "code_verifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_actor_type": { + "name": "created_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_actor_id": { + "name": "created_by_actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_session_id": { + "name": "created_by_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subject_user_id": { + "name": "subject_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subject_agent_id": { + "name": "subject_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_scopes": { + "name": "requested_scopes", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "return_to": { + "name": "return_to", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "interaction_id": { + "name": "interaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_oauth_states_company_idx": { + "name": "tool_oauth_states_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_oauth_states_connection_idx": { + "name": "tool_oauth_states_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_oauth_states_actor_idx": { + "name": "tool_oauth_states_actor_idx", + "columns": [ + { + "expression": "created_by_actor_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_by_actor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_oauth_states_subject_agent_idx": { + "name": "tool_oauth_states_subject_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_oauth_states_expires_at_idx": { + "name": "tool_oauth_states_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_oauth_states_company_id_companies_id_fk": { + "name": "tool_oauth_states_company_id_companies_id_fk", + "tableFrom": "tool_oauth_states", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_oauth_states_connection_id_tool_connections_id_fk": { + "name": "tool_oauth_states_connection_id_tool_connections_id_fk", + "tableFrom": "tool_oauth_states", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_oauth_states_subject_agent_id_agents_id_fk": { + "name": "tool_oauth_states_subject_agent_id_agents_id_fk", + "tableFrom": "tool_oauth_states", + "tableTo": "agents", + "columnsFrom": [ + "subject_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_policies": { + "name": "tool_policies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "policy_type": { + "name": "policy_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 100 + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "selectors": { + "name": "selectors", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "conditions": { + "name": "conditions", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_policies_company_enabled_idx": { + "name": "tool_policies_company_enabled_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_policies_company_type_idx": { + "name": "tool_policies_company_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "policy_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_policies_company_name_uq": { + "name": "tool_policies_company_name_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_policies_company_id_companies_id_fk": { + "name": "tool_policies_company_id_companies_id_fk", + "tableFrom": "tool_policies", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_policies_created_by_agent_id_agents_id_fk": { + "name": "tool_policies_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_policies", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_profile_bindings": { + "name": "tool_profile_bindings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "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 + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 100 + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_profile_bindings_company_target_idx": { + "name": "tool_profile_bindings_company_target_idx", + "columns": [ + { + "expression": "company_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": {} + }, + "tool_profile_bindings_target_profile_uq": { + "name": "tool_profile_bindings_target_profile_uq", + "columns": [ + { + "expression": "company_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" + }, + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_profile_bindings_company_id_companies_id_fk": { + "name": "tool_profile_bindings_company_id_companies_id_fk", + "tableFrom": "tool_profile_bindings", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_profile_bindings_profile_id_tool_profiles_id_fk": { + "name": "tool_profile_bindings_profile_id_tool_profiles_id_fk", + "tableFrom": "tool_profile_bindings", + "tableTo": "tool_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_profile_bindings_created_by_agent_id_agents_id_fk": { + "name": "tool_profile_bindings_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_profile_bindings", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_profile_entries": { + "name": "tool_profile_entries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "selector_type": { + "name": "selector_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "effect": { + "name": "effect", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'include'" + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "catalog_entry_id": { + "name": "catalog_entry_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "risk_level": { + "name": "risk_level", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "conditions": { + "name": "conditions", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_profile_entries_company_profile_idx": { + "name": "tool_profile_entries_company_profile_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_profile_entries_application_idx": { + "name": "tool_profile_entries_application_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "application_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_profile_entries_connection_idx": { + "name": "tool_profile_entries_connection_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_profile_entries_catalog_entry_idx": { + "name": "tool_profile_entries_catalog_entry_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "catalog_entry_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_profile_entries_company_id_companies_id_fk": { + "name": "tool_profile_entries_company_id_companies_id_fk", + "tableFrom": "tool_profile_entries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_profile_entries_profile_id_tool_profiles_id_fk": { + "name": "tool_profile_entries_profile_id_tool_profiles_id_fk", + "tableFrom": "tool_profile_entries", + "tableTo": "tool_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_profile_entries_application_id_tool_applications_id_fk": { + "name": "tool_profile_entries_application_id_tool_applications_id_fk", + "tableFrom": "tool_profile_entries", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_profile_entries_connection_id_tool_connections_id_fk": { + "name": "tool_profile_entries_connection_id_tool_connections_id_fk", + "tableFrom": "tool_profile_entries", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_profile_entries_catalog_entry_id_tool_catalog_entries_id_fk": { + "name": "tool_profile_entries_catalog_entry_id_tool_catalog_entries_id_fk", + "tableFrom": "tool_profile_entries", + "tableTo": "tool_catalog_entries", + "columnsFrom": [ + "catalog_entry_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_profiles": { + "name": "tool_profiles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "profile_key": { + "name": "profile_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "default_action": { + "name": "default_action", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'deny'" + }, + "new_tools_reviewed_at": { + "name": "new_tools_reviewed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "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()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_profiles_company_status_idx": { + "name": "tool_profiles_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_profiles_company_key_uq": { + "name": "tool_profiles_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "profile_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_profiles_company_name_uq": { + "name": "tool_profiles_company_name_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_profiles_company_id_companies_id_fk": { + "name": "tool_profiles_company_id_companies_id_fk", + "tableFrom": "tool_profiles", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_rate_limit_counters": { + "name": "tool_rate_limit_counters", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "policy_id": { + "name": "policy_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "counter_key": { + "name": "counter_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_kind": { + "name": "window_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_start_at": { + "name": "window_start_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "limit": { + "name": "limit", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "remaining": { + "name": "remaining", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "reset_at": { + "name": "reset_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": { + "tool_rate_limit_counters_company_idx": { + "name": "tool_rate_limit_counters_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_rate_limit_counters_window_uq": { + "name": "tool_rate_limit_counters_window_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "policy_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "counter_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_start_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_rate_limit_counters_company_id_companies_id_fk": { + "name": "tool_rate_limit_counters_company_id_companies_id_fk", + "tableFrom": "tool_rate_limit_counters", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_rate_limit_counters_policy_id_tool_policies_id_fk": { + "name": "tool_rate_limit_counters_policy_id_tool_policies_id_fk", + "tableFrom": "tool_rate_limit_counters", + "tableTo": "tool_policies", + "columnsFrom": [ + "policy_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_runtime_metric_counters": { + "name": "tool_runtime_metric_counters", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bucket_start_at": { + "name": "bucket_start_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_runtime_metric_counters_company_metric_idx": { + "name": "tool_runtime_metric_counters_company_metric_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bucket_start_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_runtime_metric_counters_bucket_uq": { + "name": "tool_runtime_metric_counters_bucket_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bucket_start_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_runtime_metric_counters_company_id_companies_id_fk": { + "name": "tool_runtime_metric_counters_company_id_companies_id_fk", + "tableFrom": "tool_runtime_metric_counters", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_runtime_slots": { + "name": "tool_runtime_slots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_scope_type": { + "name": "owner_scope_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'connection'" + }, + "owner_scope_id": { + "name": "owner_scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "runtime_kind": { + "name": "runtime_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_stdio'" + }, + "slot_key": { + "name": "slot_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'stopped'" + }, + "reuse_key": { + "name": "reuse_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_scope": { + "name": "workspace_scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_scope_hash": { + "name": "credential_scope_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_ref": { + "name": "provider_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "process_id": { + "name": "process_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "command_template_key": { + "name": "command_template_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unchecked'" + }, + "health_message": { + "name": "health_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_health_check_at": { + "name": "last_health_check_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_started_at": { + "name": "last_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "stopped_at": { + "name": "stopped_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "idle_expires_at": { + "name": "idle_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "idle_deadline_at": { + "name": "idle_deadline_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_runtime_slots_company_idx": { + "name": "tool_runtime_slots_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_runtime_slots_connection_idx": { + "name": "tool_runtime_slots_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_runtime_slots_execution_workspace_idx": { + "name": "tool_runtime_slots_execution_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_runtime_slots_slot_key_uq": { + "name": "tool_runtime_slots_slot_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slot_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_runtime_slots_company_id_companies_id_fk": { + "name": "tool_runtime_slots_company_id_companies_id_fk", + "tableFrom": "tool_runtime_slots", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_runtime_slots_application_id_tool_applications_id_fk": { + "name": "tool_runtime_slots_application_id_tool_applications_id_fk", + "tableFrom": "tool_runtime_slots", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_runtime_slots_connection_id_tool_connections_id_fk": { + "name": "tool_runtime_slots_connection_id_tool_connections_id_fk", + "tableFrom": "tool_runtime_slots", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_runtime_slots_project_workspace_id_project_workspaces_id_fk": { + "name": "tool_runtime_slots_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "tool_runtime_slots", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_runtime_slots_execution_workspace_id_execution_workspaces_id_fk": { + "name": "tool_runtime_slots_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "tool_runtime_slots", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_runtime_slots_issue_id_issues_id_fk": { + "name": "tool_runtime_slots_issue_id_issues_id_fk", + "tableFrom": "tool_runtime_slots", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_stdio_command_templates": { + "name": "tool_stdio_command_templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "template_key": { + "name": "template_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "args": { + "name": "args", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "env_keys": { + "name": "env_keys", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "tools": { + "name": "tools", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "disabled_at": { + "name": "disabled_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": { + "tool_stdio_command_templates_company_idx": { + "name": "tool_stdio_command_templates_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_stdio_command_templates_company_status_idx": { + "name": "tool_stdio_command_templates_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_stdio_command_templates_company_key_uq": { + "name": "tool_stdio_command_templates_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "template_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_stdio_command_templates_company_id_companies_id_fk": { + "name": "tool_stdio_command_templates_company_id_companies_id_fk", + "tableFrom": "tool_stdio_command_templates", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_stdio_command_templates_created_by_agent_id_agents_id_fk": { + "name": "tool_stdio_command_templates_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_stdio_command_templates", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_inbox_agent_policies": { + "name": "user_inbox_agent_policies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "allowed_agent_ids": { + "name": "allowed_agent_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()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_inbox_agent_policies_company_user_uq": { + "name": "user_inbox_agent_policies_company_user_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_inbox_agent_policies_allowed_agent_ids_idx": { + "name": "user_inbox_agent_policies_allowed_agent_ids_idx", + "columns": [ + { + "expression": "allowed_agent_ids", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "user_inbox_agent_policies_company_id_companies_id_fk": { + "name": "user_inbox_agent_policies_company_id_companies_id_fk", + "tableFrom": "user_inbox_agent_policies", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "user_inbox_agent_policies_mode_check": { + "name": "user_inbox_agent_policies_mode_check", + "value": "\"user_inbox_agent_policies\".\"mode\" in ('open', 'allowlist', 'disabled')" + } + }, + "isRLSEnabled": false + }, + "public.user_secret_declarations": { + "name": "user_secret_declarations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_secret_definition_id": { + "name": "user_secret_definition_id", + "type": "uuid", + "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 + }, + "config_path": { + "name": "config_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env_key": { + "name": "env_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version_selector": { + "name": "version_selector", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'latest'" + }, + "required": { + "name": "required", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "allow_missing_override": { + "name": "allow_missing_override", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "label": { + "name": "label", + "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": { + "user_secret_declarations_company_idx": { + "name": "user_secret_declarations_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_declarations_definition_idx": { + "name": "user_secret_declarations_definition_idx", + "columns": [ + { + "expression": "user_secret_definition_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_declarations_target_idx": { + "name": "user_secret_declarations_target_idx", + "columns": [ + { + "expression": "company_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": {} + }, + "user_secret_declarations_company_required_idx": { + "name": "user_secret_declarations_company_required_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "required", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_declarations_target_path_uq": { + "name": "user_secret_declarations_target_path_uq", + "columns": [ + { + "expression": "company_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" + }, + { + "expression": "config_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_declarations_required_override_idx": { + "name": "user_secret_declarations_required_override_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "allow_missing_override", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"user_secret_declarations\".\"allow_missing_override\" = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_secret_declarations_company_id_companies_id_fk": { + "name": "user_secret_declarations_company_id_companies_id_fk", + "tableFrom": "user_secret_declarations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_secret_declarations_user_secret_definition_id_user_secret_definitions_id_fk": { + "name": "user_secret_declarations_user_secret_definition_id_user_secret_definitions_id_fk", + "tableFrom": "user_secret_declarations", + "tableTo": "user_secret_definitions", + "columnsFrom": [ + "user_secret_definition_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_secret_definitions": { + "name": "user_secret_definitions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_encrypted'" + }, + "managed_mode": { + "name": "managed_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip_managed'" + }, + "provider_config_id": { + "name": "provider_config_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider_metadata": { + "name": "provider_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "usage_guidance": { + "name": "usage_guidance", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_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": { + "user_secret_definitions_company_status_idx": { + "name": "user_secret_definitions_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_definitions_company_provider_idx": { + "name": "user_secret_definitions_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_definitions_provider_config_idx": { + "name": "user_secret_definitions_provider_config_idx", + "columns": [ + { + "expression": "provider_config_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_definitions_company_key_uq": { + "name": "user_secret_definitions_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"user_secret_definitions\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_secret_definitions_company_id_companies_id_fk": { + "name": "user_secret_definitions_company_id_companies_id_fk", + "tableFrom": "user_secret_definitions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_secret_definitions_provider_config_id_company_secret_provider_configs_id_fk": { + "name": "user_secret_definitions_provider_config_id_company_secret_provider_configs_id_fk", + "tableFrom": "user_secret_definitions", + "tableTo": "company_secret_provider_configs", + "columnsFrom": [ + "provider_config_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "user_secret_definitions_created_by_agent_id_agents_id_fk": { + "name": "user_secret_definitions_created_by_agent_id_agents_id_fk", + "tableFrom": "user_secret_definitions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "user_secret_definitions_updated_by_agent_id_agents_id_fk": { + "name": "user_secret_definitions_updated_by_agent_id_agents_id_fk", + "tableFrom": "user_secret_definitions", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_sidebar_preferences": { + "name": "user_sidebar_preferences", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "company_order": { + "name": "company_order", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_sidebar_preferences_user_uq": { + "name": "user_sidebar_preferences_user_uq", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.work_assessments": { + "name": "work_assessments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "turn_id": { + "name": "turn_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "contract_id": { + "name": "contract_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "result_id": { + "name": "result_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trigger_kind": { + "name": "trigger_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trigger_ref": { + "name": "trigger_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trigger_capability": { + "name": "trigger_capability", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trigger_actor_company_id": { + "name": "trigger_actor_company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "prior_issue_status": { + "name": "prior_issue_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prior_status_version": { + "name": "prior_status_version", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "prior_decision_id": { + "name": "prior_decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "policy_version": { + "name": "policy_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "assessment_json": { + "name": "assessment_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "input_digest": { + "name": "input_digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "supersedes_assessment_id": { + "name": "supersedes_assessment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "work_assessments_company_issue_input_uq": { + "name": "work_assessments_company_issue_input_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "input_digest", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "work_assessments_company_id_companies_id_fk": { + "name": "work_assessments_company_id_companies_id_fk", + "tableFrom": "work_assessments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "work_assessments_trigger_actor_company_id_companies_id_fk": { + "name": "work_assessments_trigger_actor_company_id_companies_id_fk", + "tableFrom": "work_assessments", + "tableTo": "companies", + "columnsFrom": [ + "trigger_actor_company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "work_assessments_issue_company_fk": { + "name": "work_assessments_issue_company_fk", + "tableFrom": "work_assessments", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "work_assessments_run_owner_fk": { + "name": "work_assessments_run_owner_fk", + "tableFrom": "work_assessments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id" + ], + "columnsTo": [ + "company_id", + "native_issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "work_assessments_contract_owner_fk": { + "name": "work_assessments_contract_owner_fk", + "tableFrom": "work_assessments", + "tableTo": "completion_contracts", + "columnsFrom": [ + "company_id", + "issue_id", + "contract_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "work_assessments_result_owner_fk": { + "name": "work_assessments_result_owner_fk", + "tableFrom": "work_assessments", + "tableTo": "native_run_results", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "result_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "run_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "work_assessments_supersedes_owner_fk": { + "name": "work_assessments_supersedes_owner_fk", + "tableFrom": "work_assessments", + "tableTo": "work_assessments", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "supersedes_assessment_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "run_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "work_assessments_company_issue_run_id_uq": { + "name": "work_assessments_company_issue_run_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "issue_id", + "run_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "work_assessments_trigger_actor_company_check": { + "name": "work_assessments_trigger_actor_company_check", + "value": "\"work_assessments\".\"trigger_actor_company_id\" = \"work_assessments\".\"company_id\"" + } + }, + "isRLSEnabled": false + }, + "public.workspace_operations": { + "name": "workspace_operations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "exit_code": { + "name": "exit_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "log_store": { + "name": "log_store", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_ref": { + "name": "log_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_bytes": { + "name": "log_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "log_sha256": { + "name": "log_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_compressed": { + "name": "log_compressed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "stdout_excerpt": { + "name": "stdout_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stderr_excerpt": { + "name": "stderr_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_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": { + "workspace_operations_company_run_started_idx": { + "name": "workspace_operations_company_run_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_operations_company_workspace_started_idx": { + "name": "workspace_operations_company_workspace_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_operations_company_workspace_issue_started_idx": { + "name": "workspace_operations_company_workspace_issue_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_operations_company_id_companies_id_fk": { + "name": "workspace_operations_company_id_companies_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_operations_execution_workspace_id_execution_workspaces_id_fk": { + "name": "workspace_operations_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_operations_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "workspace_operations_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_operations_issue_id_issues_id_fk": { + "name": "workspace_operations_issue_id_issues_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_runtime_services": { + "name": "workspace_runtime_services", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "service_name": { + "name": "service_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lifecycle": { + "name": "lifecycle", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reuse_key": { + "name": "reuse_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_ref": { + "name": "provider_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "started_by_run_id": { + "name": "started_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "stopped_at": { + "name": "stopped_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "stop_policy": { + "name": "stop_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "exposure": { + "name": "exposure", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "exposure_handle": { + "name": "exposure_handle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "backend_url": { + "name": "backend_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "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": { + "workspace_runtime_services_company_workspace_status_idx": { + "name": "workspace_runtime_services_company_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_execution_workspace_status_idx": { + "name": "workspace_runtime_services_company_execution_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_project_status_idx": { + "name": "workspace_runtime_services_company_project_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_run_idx": { + "name": "workspace_runtime_services_run_idx", + "columns": [ + { + "expression": "started_by_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_updated_idx": { + "name": "workspace_runtime_services_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_runtime_services_company_id_companies_id_fk": { + "name": "workspace_runtime_services_company_id_companies_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workspace_runtime_services_project_id_projects_id_fk": { + "name": "workspace_runtime_services_project_id_projects_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_project_workspace_id_project_workspaces_id_fk": { + "name": "workspace_runtime_services_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_execution_workspace_id_execution_workspaces_id_fk": { + "name": "workspace_runtime_services_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_issue_id_issues_id_fk": { + "name": "workspace_runtime_services_issue_id_issues_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_owner_agent_id_agents_id_fk": { + "name": "workspace_runtime_services_owner_agent_id_agents_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_started_by_run_id_heartbeat_runs_id_fk": { + "name": "workspace_runtime_services_started_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "started_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.task_repository_bindings": { + "name": "task_repository_bindings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "repo_url": { + "name": "repo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_ref": { + "name": "repo_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "setup_complete": { + "name": "setup_complete", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "retired_at": { + "name": "retired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "checkpoint_key": { + "name": "checkpoint_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "checkpoint_sha256": { + "name": "checkpoint_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "checkpoint_at": { + "name": "checkpoint_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": {}, + "foreignKeys": { + "task_repository_bindings_company_id_companies_id_fk": { + "name": "task_repository_bindings_company_id_companies_id_fk", + "tableFrom": "task_repository_bindings", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "task_repository_bindings_task_id_issues_id_fk": { + "name": "task_repository_bindings_task_id_issues_id_fk", + "tableFrom": "task_repository_bindings", + "tableTo": "issues", + "columnsFrom": [ + "task_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "task_repository_bindings_workspace_uq": { + "name": "task_repository_bindings_workspace_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "task_id", + "workspace_id" + ] + }, + "task_repository_bindings_name_uq": { + "name": "task_repository_bindings_name_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "task_id", + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.work_file_operations": { + "name": "work_file_operations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "folder_id": { + "name": "folder_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "operation_id": { + "name": "operation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fingerprint": { + "name": "fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "work_file_operations_company_id_folder_id_work_folders_company_id_id_fk": { + "name": "work_file_operations_company_id_folder_id_work_folders_company_id_id_fk", + "tableFrom": "work_file_operations", + "tableTo": "work_folders", + "columnsFrom": [ + "company_id", + "folder_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "work_file_operations_receipt_uq": { + "name": "work_file_operations_receipt_uq", + "nullsNotDistinct": false, + "columns": [ + "folder_id", + "operation_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.work_files": { + "name": "work_files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "folder_id": { + "name": "folder_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'file'" + }, + "object_key": { + "name": "object_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "byte_size": { + "name": "byte_size", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "sha256": { + "name": "sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'application/octet-stream'" + }, + "executable": { + "name": "executable", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "work_files_folder_path_uq": { + "name": "work_files_folder_path_uq", + "columns": [ + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"work_files\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "work_files_company_folder_idx": { + "name": "work_files_company_folder_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "work_files_company_id_folder_id_work_folders_company_id_id_fk": { + "name": "work_files_company_id_folder_id_work_folders_company_id_id_fk", + "tableFrom": "work_files", + "tableTo": "work_folders", + "columnsFrom": [ + "company_id", + "folder_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.work_folder_objects": { + "name": "work_folder_objects", + "schema": "", + "columns": { + "object_key": { + "name": "object_key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "folder_id": { + "name": "folder_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "repository_binding_id": { + "name": "repository_binding_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "delete_after": { + "name": "delete_after", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "work_folder_objects_cleanup_idx": { + "name": "work_folder_objects_cleanup_idx", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "delete_after", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.work_folder_runs": { + "name": "work_folder_runs", + "schema": "", + "columns": { + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "manifest": { + "name": "manifest", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "baselines": { + "name": "baselines", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "pending_operations": { + "name": "pending_operations", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'starting'" + }, + "last_saved_at": { + "name": "last_saved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_requested": { + "name": "refresh_requested", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "work_folder_runs_company_idx": { + "name": "work_folder_runs_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "work_folder_runs_run_id_heartbeat_runs_id_fk": { + "name": "work_folder_runs_run_id_heartbeat_runs_id_fk", + "tableFrom": "work_folder_runs", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "work_folder_runs_company_id_companies_id_fk": { + "name": "work_folder_runs_company_id_companies_id_fk", + "tableFrom": "work_folder_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.work_folders": { + "name": "work_folders", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "imported_at": { + "name": "imported_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": {}, + "foreignKeys": { + "work_folders_company_id_companies_id_fk": { + "name": "work_folders_company_id_companies_id_fk", + "tableFrom": "work_folders", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "work_folders_owner_uq": { + "name": "work_folders_owner_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "scope", + "owner_id" + ] + }, + "work_folders_company_id_uq": { + "name": "work_folders_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": { + "public.chat_telegram_draft_ids": { + "name": "chat_telegram_draft_ids", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/db/src/migrations/meta/_journal.json b/packages/db/src/migrations/meta/_journal.json index 26c4631707..8877680b56 100644 --- a/packages/db/src/migrations/meta/_journal.json +++ b/packages/db/src/migrations/meta/_journal.json @@ -1916,8 +1916,22 @@ { "idx": 275, "version": "7", - "when": 1789237657148, - "tag": "0275_sandbox_work_folders", + "when": 1789221632037, + "tag": "0275_easy_dragon_man", + "breakpoints": true + }, + { + "idx": 276, + "version": "7", + "when": 1789245055965, + "tag": "0276_hard_mandroid", + "breakpoints": true + }, + { + "idx": 277, + "version": "7", + "when": 1789254591985, + "tag": "0277_sandbox_work_folders", "breakpoints": true } ] diff --git a/packages/db/src/schema/adapter_auth_sessions.ts b/packages/db/src/schema/adapter_auth_sessions.ts index f77ced341a..b7faaff6bf 100644 --- a/packages/db/src/schema/adapter_auth_sessions.ts +++ b/packages/db/src/schema/adapter_auth_sessions.ts @@ -48,6 +48,10 @@ export const adapterAuthSessions = pgTable( id: uuid("id").primaryKey().defaultRandom(), companyId: uuid("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }), environmentId: uuid("environment_id").notNull().references(() => environments.id, { onDelete: "cascade" }), + aiConnection: jsonb("ai_connection").$type(), + connectionId: uuid("connection_id"), + connectionGrantId: uuid("connection_grant_id"), + connectionMethod: text("connection_method"), adapterType: text("adapter_type").$type().notNull(), // The immutable owner principal. The service sets this column one time at // create and never updates it. The service returns the prompt only to this diff --git a/packages/db/src/schema/ai_connection_defaults.ts b/packages/db/src/schema/ai_connection_defaults.ts new file mode 100644 index 0000000000..3dd1a7463b --- /dev/null +++ b/packages/db/src/schema/ai_connection_defaults.ts @@ -0,0 +1,52 @@ +import { + foreignKey, + pgTable, + text, + timestamp, + uniqueIndex, + uuid, + check, +} from "drizzle-orm/pg-core"; +import { sql } from "drizzle-orm"; +import type { AiProvider, AiAuthMethod } from "@paperclipai/shared"; +import { companies } from "./companies.js"; +import { connectionGrants } from "./tool_access.js"; + +/** A retained row with a null/revoked grant is an unavailable default, never permission to auto-select. */ +export const aiConnectionDefaults = pgTable( + "ai_connection_defaults", + { + id: uuid("id").primaryKey().defaultRandom(), + companyId: uuid("company_id") + .notNull() + .references(() => companies.id, { onDelete: "cascade" }), + userId: text("user_id").notNull(), + provider: text("provider").$type().notNull(), + method: text("method").$type().notNull(), + grantId: uuid("grant_id"), + updatedAt: timestamp("updated_at", { withTimezone: true }) + .notNull() + .defaultNow(), + }, + (t) => [ + uniqueIndex("ai_connection_defaults_owner_method_uq").on( + t.companyId, + t.userId, + t.provider, + t.method, + ), + foreignKey({ + columns: [t.companyId, t.grantId], + foreignColumns: [connectionGrants.companyId, connectionGrants.id], + name: "ai_connection_defaults_company_grant_fk", + }), + check( + "ai_connection_defaults_provider_check", + sql`${t.provider} in ('anthropic','openai','openrouter','xai')`, + ), + check( + "ai_connection_defaults_method_check", + sql`${t.method} in ('subscription','api_key')`, + ), + ], +); diff --git a/packages/db/src/schema/chat_channels.ts b/packages/db/src/schema/chat_channels.ts index 0d6191a787..cfcfc06ad1 100644 --- a/packages/db/src/schema/chat_channels.ts +++ b/packages/db/src/schema/chat_channels.ts @@ -116,7 +116,7 @@ export const chatEndpoints = pgTable( check("chat_endpoints_email_policy_check", sql`${table.provider} <> 'agentmail' or (${table.publicationMode} = 'explicit' and ${table.externalExecutionPolicy} = 'agent')`), check( "chat_endpoints_provider_check", - sql`${table.provider} in ('slack', 'github', 'discord', 'microsoft-teams', 'telegram', 'agentmail')`, + sql`${table.provider} in ('slack', 'github', 'discord', 'microsoft-teams', 'telegram', 'agentmail', 'imessage-photon')`, ), check( "chat_endpoints_status_check", @@ -155,6 +155,9 @@ export const chatEndpoints = pgTable( // one native bot identity. Excluding providerAccountId closes the race // where concurrent setup in two guilds could otherwise claim that bot for // two Paperclip agents after both application-level prechecks passed. + uniqueIndex("chat_endpoints_photon_number_uq") + .on(table.botExternalId) + .where(sql`${table.provider} = 'imessage-photon' and ${table.status} <> 'archived' and ${table.botExternalId} is not null`), uniqueIndex("chat_endpoints_live_discord_bot_external_uq") .on(table.provider, table.botExternalId) .where( @@ -278,7 +281,7 @@ export const chatExternalPrincipals = pgTable( (table) => [ check( "chat_external_principals_provider_check", - sql`${table.provider} in ('slack', 'github', 'discord', 'microsoft-teams', 'telegram', 'agentmail')`, + sql`${table.provider} in ('slack', 'github', 'discord', 'microsoft-teams', 'telegram', 'agentmail', 'imessage-photon')`, ), check( "chat_external_principals_kind_check", diff --git a/packages/db/src/schema/index.ts b/packages/db/src/schema/index.ts index 7c253c6680..828027fc5d 100644 --- a/packages/db/src/schema/index.ts +++ b/packages/db/src/schema/index.ts @@ -34,7 +34,7 @@ export { environments } from "./environments.js"; export { environmentLeases } from "./environment_leases.js"; export { environmentCustomImageTemplates } from "./environment_custom_image_templates.js"; export { environmentCustomImageSetupSessions } from "./environment_custom_image_setup_sessions.js"; -export { adapterAuthSessions } from "./adapter_auth_sessions.js"; +export { adapterAuthSessions, ADAPTER_AUTH_SESSION_ACTIVE_STATES } from "./adapter_auth_sessions.js"; export { workspaceOperations } from "./workspace_operations.js"; export { workspaceRuntimeServices } from "./workspace_runtime_services.js"; export { projectGoals } from "./project_goals.js"; @@ -206,4 +206,5 @@ export { chatTeamsFileTransfers } from "./chat_teams_file_transfers.js"; export { chatDiscordCommandOwners } from "./chat_discord_command_owners.js"; export { chatTelegramDraftIds } from "./chat_telegram_draft_ids.js"; +export { aiConnectionDefaults } from "./ai_connection_defaults.js"; export * from "./email.js"; diff --git a/packages/db/src/schema/tool_access.ts b/packages/db/src/schema/tool_access.ts index d591bed4e8..09cb46bfc1 100644 --- a/packages/db/src/schema/tool_access.ts +++ b/packages/db/src/schema/tool_access.ts @@ -146,12 +146,14 @@ export const toolConnections = pgTable( }, (table) => [ check("tool_connections_ownership_check", sql`${table.ownership} in ('platform_shared', 'platform_provisioned', 'customer', 'dcr')`), - check("tool_connections_transport_check", sql`${table.transport} in ('mcp_remote', 'rest_api', 'local_stdio', 'chat_sdk')`), - check("tool_connections_purpose_check", sql`${table.connectionPurpose} in ('tool', 'channel')`), + check("tool_connections_transport_check", sql`${table.transport} in ('mcp_remote', 'rest_api', 'local_stdio', 'chat_sdk', 'runtime_auth')`), + check("tool_connections_purpose_check", sql`${table.connectionPurpose} in ('tool', 'channel', 'ai')`), check("tool_connections_channel_transport_check", sql`( - (${table.connectionPurpose} = 'tool' and ${table.transport} <> 'chat_sdk') + (${table.connectionPurpose} = 'tool' and ${table.transport} not in ('chat_sdk', 'runtime_auth')) or (${table.connectionPurpose} = 'channel' and (${table.transport} = 'chat_sdk' or (${table.transport} = 'rest_api' and ${table.config}->>'provider' = 'agentmail'))) + or + (${table.connectionPurpose} = 'ai' and ${table.transport} = 'runtime_auth') )`), check("tool_connections_auth_kind_check", sql`${table.authKind} in ('oauth', 'api_key', 'none')`), check("tool_connections_credential_source_check", sql`${table.credentialSource} in ('paperclip_vault', 'vercel_connect')`), diff --git a/packages/paperclip-runner/src/drivers/acpx/codex-credentials.test.ts b/packages/paperclip-runner/src/drivers/acpx/codex-credentials.test.ts index 214f1a4b00..8a18b20012 100644 --- a/packages/paperclip-runner/src/drivers/acpx/codex-credentials.test.ts +++ b/packages/paperclip-runner/src/drivers/acpx/codex-credentials.test.ts @@ -168,12 +168,10 @@ describe("managed Codex credentials", () => { ] as const)( "tolerates one unrelated silent %s quorum listener", async (_label, occupiedIndex) => { - const prepared = - occupiedIndex === 0 ? await silentPrimaryQuorumFixture() : null; - const fixture = prepared?.fixture ?? (await credentialFixture()); - const ports = credentialLeasePorts(await realpath(fixture.home)); - const occupied = - prepared?.occupied ?? (await listenSilently(ports[occupiedIndex])); + const { fixture, occupied } = await silentPrimaryQuorumFixture( + credentialFixture, + occupiedIndex, + ); try { const lease = await stageManagedCodexCredential({ agentHomeDirectory: fixture.home, diff --git a/packages/paperclip-runner/test/fixtures/fake-final-burst-codex-app-server.mjs b/packages/paperclip-runner/test/fixtures/fake-final-burst-codex-app-server.mjs index fbe492a852..780286dafc 100644 --- a/packages/paperclip-runner/test/fixtures/fake-final-burst-codex-app-server.mjs +++ b/packages/paperclip-runner/test/fixtures/fake-final-burst-codex-app-server.mjs @@ -87,15 +87,27 @@ createInterface({ input: process.stdin }).on("line", (line) => { modelProvider: "fixture-no-provider", thread: { id: state.threadId, - status: { type: Object.values(state.turns).some(turn => turn.status === "inProgress") ? "active" : "idle" }, sessionId: "final-burst-fixture", - turns: Object.entries(state.turns).map(([turnId, turn]) => ({ - id: turnId, - status: turn.status, - })), + status: { + type: Object.values(state.turns).some((turn) => turn.status === "inProgress") ? "active" : "idle", + }, + ...(params.includeTurns ? { + turns: Object.entries(state.turns).map(([turnId, turn]) => ({ id: turnId, status: turn.status })), + } : {}), }, }, }); + } else if (method === "thread/turns/list") { + const turns = Object.entries(state.turns).map(([turnId, turn]) => ({ + id: turnId, status: turn.status, items: [], itemsView: "notLoaded", + })); + if (params.sortDirection === "desc") turns.reverse(); + const offset = Number(params.cursor ?? 0); + const limit = params.limit ?? 100; + send({ id, result: { + data: turns.slice(offset, offset + limit), + nextCursor: offset + limit < turns.length ? String(offset + limit) : null, + } }); } else if (method === "turn/start") { const turnId = `final-burst-turn-${++state.nextTurn}`; state.turns[turnId] = { status: "inProgress", startedAtMs: Date.now() }; diff --git a/packages/plugins/sandbox-providers/daytona/pnpm-workspace.yaml b/packages/plugins/sandbox-providers/daytona/pnpm-workspace.yaml new file mode 100644 index 0000000000..7a2a114c0b --- /dev/null +++ b/packages/plugins/sandbox-providers/daytona/pnpm-workspace.yaml @@ -0,0 +1,6 @@ +packages: + - '.' + +# The SDK ships generated protobuf code; its optional install script is not needed. +allowBuilds: + protobufjs: false diff --git a/packages/shared/src/ai-connections.ts b/packages/shared/src/ai-connections.ts new file mode 100644 index 0000000000..ec198505da --- /dev/null +++ b/packages/shared/src/ai-connections.ts @@ -0,0 +1,231 @@ +import { z } from "zod"; + +/** Runtime authentication is a separate transport, never a tool or channel. */ +export const connectionPurposeTransportSchema = z.discriminatedUnion( + "connectionPurpose", + [ + z.object({ + connectionPurpose: z.literal("tool"), + transport: z.enum(["mcp_remote", "rest_api", "local_stdio"]), + }), + z.object({ + connectionPurpose: z.literal("channel"), + transport: z.enum(["chat_sdk", "rest_api"]), + config: z.object({ provider: z.string().optional() }).passthrough().optional(), + }).refine( + (connection) => connection.transport === "chat_sdk" || connection.config?.provider === "agentmail", + { message: "REST channel connections require the AgentMail provider", path: ["config", "provider"] }, + ), + z.object({ + connectionPurpose: z.literal("ai"), + transport: z.literal("runtime_auth"), + }), + ], +); +export type ConnectionPurposeTransport = z.infer< + typeof connectionPurposeTransportSchema +>; + +export const AI_PROVIDERS = [ + "anthropic", + "openai", + "openrouter", + "xai", +] as const; +export const aiProviderSchema = z.enum(AI_PROVIDERS); +export const aiAuthMethodSchema = z.enum(["subscription", "api_key"]); +export type AiProvider = z.infer; +export type AiAuthMethod = z.infer; +const requirement = { provider: aiProviderSchema, method: aiAuthMethodSchema }; +export const aiConnectionBindingSchema = z.discriminatedUnion("mode", [ + z.object({ ...requirement, mode: z.literal("responsible_user") }).strict(), + z + .object({ + ...requirement, + mode: z.literal("shared"), + connectionId: z.string().uuid(), + grantId: z.string().uuid(), + }) + .strict(), + z + .object({ + ...requirement, + // Legacy wire format only; human access still applies. New UI never creates it. + mode: z.literal("delegated"), + connectionId: z.string().uuid(), + grantId: z.string().uuid(), + }) + .strict(), +]); +export type AiConnectionBinding = z.infer; +export const aiConnectionMetadataSchema = z.object(requirement).strict(); +export type AiConnectionMetadata = z.infer; + +/** Existing integrations only. This table describes compatibility, never routing. */ +export const AI_CONNECTION_CAPABILITIES: Record< + AiProvider, + { + name: string; + methods: Partial< + Record + >; + } +> = { + anthropic: { + name: "Claude", + methods: { + subscription: { + adapters: ["claude_local"], + envKey: "CLAUDE_CODE_OAUTH_TOKEN", + }, + api_key: { adapters: ["claude_local"], envKey: "ANTHROPIC_API_KEY" }, + }, + }, + openai: { + name: "OpenAI", + methods: { + subscription: { adapters: ["codex_local"], envKey: "CODEX_HOME" }, + api_key: { adapters: ["codex_local"], envKey: "OPENAI_API_KEY" }, + }, + }, + openrouter: { + name: "OpenRouter", + methods: { + api_key: { adapters: ["opencode_local"], envKey: "OPENROUTER_API_KEY" }, + }, + }, + xai: { + name: "Grok", + methods: { + subscription: { adapters: ["grok_local"], envKey: "GROK_HOME" }, + api_key: { adapters: ["grok_local"], envKey: "XAI_API_KEY" }, + }, + }, +}; +export function isAiConnectionCompatible( + requirement: AiConnectionMetadata, + adapterType: string, + model?: unknown, + runnerProvider?: unknown, + acpxAgent?: unknown, +): boolean { + if (adapterType === "paperclip_runner") + adapterType = + runnerProvider === "claude" || + (runnerProvider === "acpx" && acpxAgent === "claude") + ? "claude_local" + : runnerProvider === "codex" + ? "codex_local" + : runnerProvider === "opencode" + ? "opencode_local" + : "unsupported"; + const method = + AI_CONNECTION_CAPABILITIES[requirement.provider].methods[ + requirement.method + ]; + return ( + Boolean(method?.adapters.includes(adapterType)) && + (requirement.provider !== "openrouter" || + (typeof model === "string" && model.startsWith("openrouter/"))) + ); +} +export type AiConnectionUnavailableReason = + | "responsible_user_missing" + | "membership_missing" + | "default_missing" + | "connection_missing" + | "connection_unavailable" + | "incompatible" + | "access_denied" + | "credential_missing"; +export interface AiConnectionAttribution { + connectionId: string; + grantId: string; + provider: AiProvider; + method: AiAuthMethod; + mode: AiConnectionBinding["mode"]; + responsibleUserId: string | null; +} +export type AiConnectionResolution = + | { ok: true; attribution: AiConnectionAttribution } + | { ok: false; reason: AiConnectionUnavailableReason; message: string }; + +export interface AiManagedConnectionSummary { + id: string; + grantId: string; + companyId: string; + provider: AiProvider; + method: AiAuthMethod; + name: string; + accountLabel?: string; + ownership: "personal" | "shared"; + ownerUserId?: string; + ownerName?: string; + isDefault: boolean; + status: "connected" | "needs_attention" | "expired" | "revoked"; + unavailableReason?: string; +} +export const createAiConnectionSchema = z + .object({ + ...requirement, + name: z.string().trim().min(1).max(160), + ownership: z.enum(["personal", "shared"]), + apiKey: z.string().trim().min(1).max(32768).optional(), + loginSessionId: z.string().max(128).optional(), + connectionId: z.string().uuid().optional(), + agentIds: z.array(z.string().uuid()).max(1000).default([]), + allAgents: z.boolean().default(false), + }) + .strict() + .superRefine((v, ctx) => { + if (!AI_CONNECTION_CAPABILITIES[v.provider].methods[v.method]) + ctx.addIssue({ code: "custom", message: "Unsupported sign-in method" }); + if ( + v.method === "api_key" + ? !v.apiKey || Boolean(v.loginSessionId) + : !v.loginSessionId || Boolean(v.apiKey) + ) { + ctx.addIssue({ + code: "custom", + message: + "Provide exactly the credential for the selected sign-in method", + }); + } + }); +export type CreateAiConnection = z.infer; + +export const aiConnectionLoginIntentSchema = z + .object({ + provider: aiProviderSchema, + method: z.literal("subscription"), + name: z.string().trim().min(1).max(160), + ownership: z.enum(["personal", "shared"]), + connectionId: z.string().uuid().optional(), + agentIds: z.array(z.string().uuid()).max(1000).default([]), + allAgents: z.boolean().default(false), + }) + .strict(); +export type AiConnectionLoginIntent = z.infer< + typeof aiConnectionLoginIntentSchema +>; + +export const localAiConnectionSchema = aiConnectionLoginIntentSchema.extend({ + localSessionId: z.string().uuid().optional(), +}); +export const localAiLoginStartSchema = aiConnectionLoginIntentSchema.extend({ restart: z.boolean().optional() }); +export interface LocalAiLoginStatus { + status: "ready" | "sign_in_required" | "expired"; +} +export interface LocalAiLoginAttempt { + sessionId: string; + command: string; + expiresAt: string; +} + +/** Preview-era copies of rotating local credentials must be reconnected. */ +export function aiSubscriptionNeedsIsolatedLogin(config: Record | undefined): boolean { + const metadata = aiConnectionMetadataSchema.safeParse(config?.ai); + return metadata.success && metadata.data.method === "subscription" && + (metadata.data.provider === "openai" || metadata.data.provider === "xai") && + config?.aiIsolatedSubscription !== true; +} diff --git a/packages/shared/src/app-definitions.generated.ts b/packages/shared/src/app-definitions.generated.ts index e98d19808c..fc275ec0a5 100644 --- a/packages/shared/src/app-definitions.generated.ts +++ b/packages/shared/src/app-definitions.generated.ts @@ -3,66 +3,70 @@ import a1 from "./app-definitions/zapier.json" with { type: "json" }; import a2 from "./app-definitions/github.json" with { type: "json" }; import a3 from "./app-definitions/slack.json" with { type: "json" }; import a4 from "./app-definitions/microsoft-teams.json" with { type: "json" }; -import a5 from "./app-definitions/telegram.json" with { type: "json" }; -import a6 from "./app-definitions/discord.json" with { type: "json" }; -import a7 from "./app-definitions/notion.json" with { type: "json" }; -import a8 from "./app-definitions/posthog.json" with { type: "json" }; -import a9 from "./app-definitions/linear.json" with { type: "json" }; -import a10 from "./app-definitions/context7.json" with { type: "json" }; -import a11 from "./app-definitions/shopify.json" with { type: "json" }; -import a12 from "./app-definitions/composio.json" with { type: "json" }; -import a13 from "./app-definitions/oauth-generic.json" with { type: "json" }; -import a14 from "./app-definitions/api-key-generic.json" with { type: "json" }; -import a15 from "./app-definitions/sentry.json" with { type: "json" }; -import a16 from "./app-definitions/vercel.json" with { type: "json" }; -import a17 from "./app-definitions/anthropic.json" with { type: "json" }; -import a18 from "./app-definitions/jira.json" with { type: "json" }; -import a19 from "./app-definitions/airtable.json" with { type: "json" }; -import a20 from "./app-definitions/beehiiv.json" with { type: "json" }; -import a21 from "./app-definitions/bitly.json" with { type: "json" }; -import a22 from "./app-definitions/candid.json" with { type: "json" }; -import a23 from "./app-definitions/cloudflare.json" with { type: "json" }; -import a24 from "./app-definitions/cloudinary.json" with { type: "json" }; -import a25 from "./app-definitions/coda.json" with { type: "json" }; -import a26 from "./app-definitions/hugging-face.json" with { type: "json" }; -import a27 from "./app-definitions/kernel.json" with { type: "json" }; -import a28 from "./app-definitions/local-falcon.json" with { type: "json" }; -import a29 from "./app-definitions/make.json" with { type: "json" }; -import a30 from "./app-definitions/manufact.json" with { type: "json" }; -import a31 from "./app-definitions/miro.json" with { type: "json" }; -import a32 from "./app-definitions/netlify.json" with { type: "json" }; -import a33 from "./app-definitions/oreilly.json" with { type: "json" }; -import a34 from "./app-definitions/planetscale.json" with { type: "json" }; -import a35 from "./app-definitions/resend.json" with { type: "json" }; -import a36 from "./app-definitions/ticktick.json" with { type: "json" }; -import a37 from "./app-definitions/todoist.json" with { type: "json" }; -import a38 from "./app-definitions/webflow.json" with { type: "json" }; -import a39 from "./app-definitions/wix.json" with { type: "json" }; -import a40 from "./app-definitions/brex.json" with { type: "json" }; -import a41 from "./app-definitions/clickhouse.json" with { type: "json" }; -import a42 from "./app-definitions/egnyte.json" with { type: "json" }; -import a43 from "./app-definitions/embat.json" with { type: "json" }; -import a44 from "./app-definitions/mixpanel.json" with { type: "json" }; -import a45 from "./app-definitions/postman.json" with { type: "json" }; -import a46 from "./app-definitions/razorpay.json" with { type: "json" }; -import a47 from "./app-definitions/sanity.json" with { type: "json" }; -import a48 from "./app-definitions/stripe.json" with { type: "json" }; -import a49 from "./app-definitions/supabase.json" with { type: "json" }; -import a50 from "./app-definitions/ticket-tailor.json" with { type: "json" }; -import a51 from "./app-definitions/asana.json" with { type: "json" }; -import a52 from "./app-definitions/box.json" with { type: "json" }; -import a53 from "./app-definitions/mem0.json" with { type: "json" }; -import a54 from "./app-definitions/pagerduty.json" with { type: "json" }; -import a55 from "./app-definitions/similarweb.json" with { type: "json" }; -import a56 from "./app-definitions/xero.json" with { type: "json" }; -import a57 from "./app-definitions/gmail.json" with { type: "json" }; -import a58 from "./app-definitions/google-drive.json" with { type: "json" }; -import a59 from "./app-definitions/google-docs.json" with { type: "json" }; -import a60 from "./app-definitions/google-sheets.json" with { type: "json" }; -import a61 from "./app-definitions/google-slides.json" with { type: "json" }; -import a62 from "./app-definitions/google-calendar.json" with { type: "json" }; -import a63 from "./app-definitions/google-chat.json" with { type: "json" }; -import a64 from "./app-definitions/google-people.json" with { type: "json" }; -import a65 from "./app-definitions/google-workspace-search.json" with { type: "json" }; +import a5 from "./app-definitions/imessage-photon.json" with { type: "json" }; +import a6 from "./app-definitions/telegram.json" with { type: "json" }; +import a7 from "./app-definitions/discord.json" with { type: "json" }; +import a8 from "./app-definitions/notion.json" with { type: "json" }; +import a9 from "./app-definitions/posthog.json" with { type: "json" }; +import a10 from "./app-definitions/linear.json" with { type: "json" }; +import a11 from "./app-definitions/context7.json" with { type: "json" }; +import a12 from "./app-definitions/shopify.json" with { type: "json" }; +import a13 from "./app-definitions/composio.json" with { type: "json" }; +import a14 from "./app-definitions/oauth-generic.json" with { type: "json" }; +import a15 from "./app-definitions/api-key-generic.json" with { type: "json" }; +import a16 from "./app-definitions/sentry.json" with { type: "json" }; +import a17 from "./app-definitions/vercel.json" with { type: "json" }; +import a18 from "./app-definitions/anthropic.json" with { type: "json" }; +import a19 from "./app-definitions/jira.json" with { type: "json" }; +import a20 from "./app-definitions/airtable.json" with { type: "json" }; +import a21 from "./app-definitions/beehiiv.json" with { type: "json" }; +import a22 from "./app-definitions/bitly.json" with { type: "json" }; +import a23 from "./app-definitions/candid.json" with { type: "json" }; +import a24 from "./app-definitions/cloudflare.json" with { type: "json" }; +import a25 from "./app-definitions/cloudinary.json" with { type: "json" }; +import a26 from "./app-definitions/coda.json" with { type: "json" }; +import a27 from "./app-definitions/hugging-face.json" with { type: "json" }; +import a28 from "./app-definitions/kernel.json" with { type: "json" }; +import a29 from "./app-definitions/local-falcon.json" with { type: "json" }; +import a30 from "./app-definitions/make.json" with { type: "json" }; +import a31 from "./app-definitions/manufact.json" with { type: "json" }; +import a32 from "./app-definitions/miro.json" with { type: "json" }; +import a33 from "./app-definitions/netlify.json" with { type: "json" }; +import a34 from "./app-definitions/oreilly.json" with { type: "json" }; +import a35 from "./app-definitions/planetscale.json" with { type: "json" }; +import a36 from "./app-definitions/resend.json" with { type: "json" }; +import a37 from "./app-definitions/ticktick.json" with { type: "json" }; +import a38 from "./app-definitions/todoist.json" with { type: "json" }; +import a39 from "./app-definitions/webflow.json" with { type: "json" }; +import a40 from "./app-definitions/wix.json" with { type: "json" }; +import a41 from "./app-definitions/brex.json" with { type: "json" }; +import a42 from "./app-definitions/clickhouse.json" with { type: "json" }; +import a43 from "./app-definitions/egnyte.json" with { type: "json" }; +import a44 from "./app-definitions/embat.json" with { type: "json" }; +import a45 from "./app-definitions/mixpanel.json" with { type: "json" }; +import a46 from "./app-definitions/postman.json" with { type: "json" }; +import a47 from "./app-definitions/razorpay.json" with { type: "json" }; +import a48 from "./app-definitions/sanity.json" with { type: "json" }; +import a49 from "./app-definitions/stripe.json" with { type: "json" }; +import a50 from "./app-definitions/supabase.json" with { type: "json" }; +import a51 from "./app-definitions/ticket-tailor.json" with { type: "json" }; +import a52 from "./app-definitions/asana.json" with { type: "json" }; +import a53 from "./app-definitions/box.json" with { type: "json" }; +import a54 from "./app-definitions/mem0.json" with { type: "json" }; +import a55 from "./app-definitions/pagerduty.json" with { type: "json" }; +import a56 from "./app-definitions/similarweb.json" with { type: "json" }; +import a57 from "./app-definitions/xero.json" with { type: "json" }; +import a58 from "./app-definitions/gmail.json" with { type: "json" }; +import a59 from "./app-definitions/google-drive.json" with { type: "json" }; +import a60 from "./app-definitions/google-docs.json" with { type: "json" }; +import a61 from "./app-definitions/google-sheets.json" with { type: "json" }; +import a62 from "./app-definitions/google-slides.json" with { type: "json" }; +import a63 from "./app-definitions/google-calendar.json" with { type: "json" }; +import a64 from "./app-definitions/google-chat.json" with { type: "json" }; +import a65 from "./app-definitions/google-people.json" with { type: "json" }; +import a66 from "./app-definitions/google-workspace-search.json" with { type: "json" }; +import a67 from "./app-definitions/openai.json" with { type: "json" }; +import a68 from "./app-definitions/openrouter.json" with { type: "json" }; +import a69 from "./app-definitions/xai.json" with { type: "json" }; import type { AppDefinition } from "./types/app-definition.js"; -export const APP_DEFINITIONS=[a0,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27,a28,a29,a30,a31,a32,a33,a34,a35,a36,a37,a38,a39,a40,a41,a42,a43,a44,a45,a46,a47,a48,a49,a50,a51,a52,a53,a54,a55,a56,a57,a58,a59,a60,a61,a62,a63,a64,a65] as AppDefinition[]; +export const APP_DEFINITIONS=[a0,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27,a28,a29,a30,a31,a32,a33,a34,a35,a36,a37,a38,a39,a40,a41,a42,a43,a44,a45,a46,a47,a48,a49,a50,a51,a52,a53,a54,a55,a56,a57,a58,a59,a60,a61,a62,a63,a64,a65,a66,a67,a68,a69] as AppDefinition[]; diff --git a/packages/shared/src/app-definitions.test.ts b/packages/shared/src/app-definitions.test.ts index 007e78cb14..b387e5286a 100644 --- a/packages/shared/src/app-definitions.test.ts +++ b/packages/shared/src/app-definitions.test.ts @@ -679,7 +679,7 @@ describe("AppDefinition catalog", () => { "ticktick", "xero", ]); - expect(APP_STORE_DEFINITIONS).toHaveLength(41); + expect(APP_STORE_DEFINITIONS).toHaveLength(46); const connectableSlugs = new Set( CONNECTABLE_APP_DEFINITIONS.map((entry) => entry.slug), ); @@ -691,7 +691,7 @@ describe("AppDefinition catalog", () => { expect(storeSlugs.has(slug), slug).toBe(false); } }); - it("ships complete local branding provenance for all 41 store-visible providers", () => { + it("ships complete local branding provenance for all 46 store-visible providers", () => { const uiPublic = path.resolve( path.dirname(fileURLToPath(import.meta.url)), "../../../ui/public", @@ -711,14 +711,14 @@ describe("AppDefinition catalog", () => { }>; }; const visible = manifest.providers.filter((entry) => entry.catalogVisible); - expect(visible).toHaveLength(41); + expect(visible).toHaveLength(46); expect(new Set(visible.map((entry) => entry.slug))).toHaveProperty( "size", - 41, + 46, ); expect(new Set(visible.map((entry) => entry.localAsset))).toHaveProperty( "size", - 41, + 46, ); expect(new Set(APP_STORE_DEFINITIONS.map((entry) => entry.slug))).toEqual( new Set(visible.map((entry) => entry.slug)), diff --git a/packages/shared/src/app-definitions.ts b/packages/shared/src/app-definitions.ts index bc3b485846..d0ec6c2c02 100644 --- a/packages/shared/src/app-definitions.ts +++ b/packages/shared/src/app-definitions.ts @@ -4,6 +4,7 @@ import type { AppDefinition, ConnectionMethodDef, FieldDef } from "./types/app-d import type { ToolConnectionOwnership } from "./types/tool-access.js"; export const CONNECTABLE_APP_SLUGS = new Set([ + "anthropic", "openai", "openrouter", "xai", "agentmail", ...SELF_SERVE_MCP_CANDIDATES.map((entry) => entry.slug), "zapier", @@ -27,6 +28,7 @@ export const CONNECTABLE_APP_SLUGS = new Set([ "discord", "microsoft-teams", "telegram", + "imessage-photon", ]); export const CONNECTABLE_APP_DEFINITIONS = APP_DEFINITIONS.filter((app) => @@ -170,6 +172,7 @@ export function connectionMethodAcceptsCustomerOAuthClient(method: ConnectionMet export function connectionMethodSupportsCatalogSetup(method: ConnectionMethodDef | null | undefined): boolean { if (!method) return false; + if (method.transport === "runtime_auth") return Boolean(method.ai); if (method.auth === "none" || method.auth === "api_key") return true; return connectionMethodSupportsAutomaticOAuth(method) || connectionMethodAcceptsCustomerOAuthClient(method); diff --git a/packages/shared/src/app-definitions/anthropic.json b/packages/shared/src/app-definitions/anthropic.json index a52b73a4fe..65452bf305 100644 --- a/packages/shared/src/app-definitions/anthropic.json +++ b/packages/shared/src/app-definitions/anthropic.json @@ -15,6 +15,62 @@ "https://api.anthropic.com/*" ], "methods": [ + { + "key": "ai-subscription", + "label": "Claude subscription", + "purpose": "ai", + "transport": "runtime_auth", + "auth": "oauth", + "ai": { + "provider": "anthropic", + "method": "subscription" + }, + "grantKinds": [ + "user", + "organization" + ], + "ownershipModes": [ + "customer" + ], + "whenToUse": "Authenticate an agent with this account.", + "guidanceMd": "Use your personal account or an explicitly shared company account.", + "riskTier": "S3" + }, + { + "key": "ai-api_key", + "label": "Claude API key", + "purpose": "ai", + "transport": "runtime_auth", + "auth": "api_key", + "ai": { + "provider": "anthropic", + "method": "api_key" + }, + "grantKinds": [ + "user", + "organization" + ], + "ownershipModes": [ + "customer" + ], + "whenToUse": "Authenticate an agent with this account.", + "guidanceMd": "Use your personal account or an explicitly shared company account.", + "riskTier": "S3", + "credentialFields": [ + { + "key": "apiKey", + "label": "API key", + "type": "password", + "required": true, + "placeholder": "Enter API key", + "secret": true + } + ], + "keyPlacement": { + "location": "env", + "name": "ANTHROPIC_API_KEY" + } + }, { "key": "api-key", "transport": "rest_api", diff --git a/packages/shared/src/app-definitions/imessage-photon.json b/packages/shared/src/app-definitions/imessage-photon.json new file mode 100644 index 0000000000..b5f018a9fc --- /dev/null +++ b/packages/shared/src/app-definitions/imessage-photon.json @@ -0,0 +1,50 @@ +{ + "schemaVersion": 1, + "slug": "imessage-photon", + "name": "iMessage Photon", + "description": "Message a Paperclip agent from Apple Messages using Photon Cloud. Pro supports DMs; dedicated lines also support groups.", + "categories": [ + "communication" + ], + "featured": false, + "branding": { + "logoUrl": "/brands/apps/imessage-photon.png" + }, + "urlPatterns": [ + "https://photon.codes/*" + ], + "methods": [ + { + "key": "chat-agent", + "label": "Chat with an agent", + "purpose": "channel", + "provider": "imessage-photon", + "transport": "chat_sdk", + "auth": "api_key", + "ownershipModes": [ + "customer" + ], + "whenToUse": "Let people in iMessage Photon start and continue work with one Paperclip agent.", + "credentialFields": [ + { + "key": "projectSecret", + "label": "Project secret", + "type": "password", + "required": true, + "placeholder": "Photon project secret", + "secret": true + } + ], + "guidanceMd": "Connect a Photon Cloud project. Pro shared lines support DMs after sender enrollment in Photon and identity linking in Paperclip. Dedicated lines also support individually enabled groups.", + "consoleLinks": { + "register": "https://photon.codes/", + "docs": "https://photon.codes/docs/spectrum-ts/providers/imessage/connection-and-routing" + }, + "riskTier": "S3", + "requiredResourceFilters": [ + "direct_message", + "group_chat" + ] + } + ] +} diff --git a/packages/shared/src/app-definitions/microsoft-teams.json b/packages/shared/src/app-definitions/microsoft-teams.json index fd59250399..12d0fa5eec 100644 --- a/packages/shared/src/app-definitions/microsoft-teams.json +++ b/packages/shared/src/app-definitions/microsoft-teams.json @@ -3,12 +3,16 @@ "slug": "microsoft-teams", "name": "Microsoft Teams", "description": "Let people start and continue Paperclip work with an agent from Microsoft Teams.", - "categories": ["communication"], + "categories": [ + "communication" + ], "featured": false, "branding": { "logoUrl": "/brands/apps/microsoft-teams.svg" }, - "urlPatterns": ["https://teams.microsoft.com/*"], + "urlPatterns": [ + "https://teams.microsoft.com/*" + ], "methods": [ { "key": "chat-agent", @@ -17,7 +21,9 @@ "provider": "microsoft-teams", "transport": "chat_sdk", "auth": "api_key", - "ownershipModes": ["customer"], + "ownershipModes": [ + "customer" + ], "whenToUse": "Let people in Microsoft Teams start and continue work with one Paperclip agent.", "credentialFields": [ { @@ -51,7 +57,11 @@ "docs": "https://learn.microsoft.com/en-us/microsoftteams/platform/bots/how-to/create-a-bot-for-teams" }, "riskTier": "S3", - "requiredResourceFilters": ["team", "channel", "chat"] + "requiredResourceFilters": [ + "team", + "channel", + "chat" + ] } ] } diff --git a/packages/shared/src/app-definitions/openai.json b/packages/shared/src/app-definitions/openai.json new file mode 100644 index 0000000000..e3ef286564 --- /dev/null +++ b/packages/shared/src/app-definitions/openai.json @@ -0,0 +1,74 @@ +{ + "schemaVersion": 1, + "slug": "openai", + "name": "OpenAI", + "description": "Connect OpenAI accounts for your agents.", + "categories": [ + "ai" + ], + "branding": { + "logoUrl": "/brands/apps/openai.svg", + "darkLogoUrl": "/brands/apps/openai-dark.svg" + }, + "urlPatterns": [ + "https://api.openai.com/*" + ], + "methods": [ + { + "key": "ai-subscription", + "label": "OpenAI subscription", + "purpose": "ai", + "transport": "runtime_auth", + "auth": "oauth", + "ai": { + "provider": "openai", + "method": "subscription" + }, + "grantKinds": [ + "user", + "organization" + ], + "ownershipModes": [ + "customer" + ], + "whenToUse": "Authenticate an agent with this account.", + "guidanceMd": "Use your personal account or an explicitly shared company account.", + "riskTier": "S3" + }, + { + "key": "ai-api_key", + "label": "OpenAI API key", + "purpose": "ai", + "transport": "runtime_auth", + "auth": "api_key", + "ai": { + "provider": "openai", + "method": "api_key" + }, + "grantKinds": [ + "user", + "organization" + ], + "ownershipModes": [ + "customer" + ], + "whenToUse": "Authenticate an agent with this account.", + "guidanceMd": "Use your personal account or an explicitly shared company account.", + "riskTier": "S3", + "credentialFields": [ + { + "key": "apiKey", + "label": "API key", + "type": "password", + "required": true, + "placeholder": "Enter API key", + "secret": true + } + ], + "keyPlacement": { + "location": "env", + "name": "OPENAI_API_KEY" + } + } + ] +} diff --git a/packages/shared/src/app-definitions/openrouter.json b/packages/shared/src/app-definitions/openrouter.json new file mode 100644 index 0000000000..6f95ff8a8b --- /dev/null +++ b/packages/shared/src/app-definitions/openrouter.json @@ -0,0 +1,53 @@ +{ + "schemaVersion": 1, + "slug": "openrouter", + "name": "OpenRouter", + "description": "Connect OpenRouter accounts for your agents.", + "categories": [ + "ai" + ], + "branding": { + "logoUrl": "/brands/apps/openrouter.svg", + "darkLogoUrl": "/brands/apps/openrouter-dark.svg" + }, + "urlPatterns": [ + "https://openrouter.ai/api/*" + ], + "methods": [ + { + "key": "ai-api_key", + "label": "OpenRouter API key", + "purpose": "ai", + "transport": "runtime_auth", + "auth": "api_key", + "ai": { + "provider": "openrouter", + "method": "api_key" + }, + "grantKinds": [ + "user", + "organization" + ], + "ownershipModes": [ + "customer" + ], + "whenToUse": "Authenticate an agent with this account.", + "guidanceMd": "Use your personal account or an explicitly shared company account.", + "riskTier": "S3", + "credentialFields": [ + { + "key": "apiKey", + "label": "API key", + "type": "password", + "required": true, + "placeholder": "Enter API key", + "secret": true + } + ], + "keyPlacement": { + "location": "env", + "name": "OPENROUTER_API_KEY" + } + } + ] +} diff --git a/packages/shared/src/app-definitions/xai.json b/packages/shared/src/app-definitions/xai.json new file mode 100644 index 0000000000..ee1f729db5 --- /dev/null +++ b/packages/shared/src/app-definitions/xai.json @@ -0,0 +1,74 @@ +{ + "schemaVersion": 1, + "slug": "xai", + "name": "Grok", + "description": "Connect Grok accounts for your agents.", + "categories": [ + "ai" + ], + "branding": { + "logoUrl": "/brands/apps/xai.svg", + "darkLogoUrl": "/brands/apps/xai-dark.svg" + }, + "urlPatterns": [ + "https://api.x.ai/*" + ], + "methods": [ + { + "key": "ai-subscription", + "label": "Grok subscription", + "purpose": "ai", + "transport": "runtime_auth", + "auth": "oauth", + "ai": { + "provider": "xai", + "method": "subscription" + }, + "grantKinds": [ + "user", + "organization" + ], + "ownershipModes": [ + "customer" + ], + "whenToUse": "Authenticate an agent with this account.", + "guidanceMd": "Use your personal account or an explicitly shared company account.", + "riskTier": "S3" + }, + { + "key": "ai-api_key", + "label": "Grok API key", + "purpose": "ai", + "transport": "runtime_auth", + "auth": "api_key", + "ai": { + "provider": "xai", + "method": "api_key" + }, + "grantKinds": [ + "user", + "organization" + ], + "ownershipModes": [ + "customer" + ], + "whenToUse": "Authenticate an agent with this account.", + "guidanceMd": "Use your personal account or an explicitly shared company account.", + "riskTier": "S3", + "credentialFields": [ + { + "key": "apiKey", + "label": "API key", + "type": "password", + "required": true, + "placeholder": "Enter API key", + "secret": true + } + ], + "keyPlacement": { + "location": "env", + "name": "XAI_API_KEY" + } + } + ] +} diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 463b558c54..e86cfc423b 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -2761,5 +2761,6 @@ export type { ExecutionProjection, ExecutionReconciliation, ExecutionBlocker } f export { EXECUTION_RECONCILIATION_CAUSES, requiresExecutionReconciliation } from "./types/execution-projection.js"; +export * from "./ai-connections.js"; export * from "./types/email.js"; export * from "./validators/email.js"; diff --git a/packages/shared/src/types/agent.ts b/packages/shared/src/types/agent.ts index 8079f60fc1..736eed8a2b 100644 --- a/packages/shared/src/types/agent.ts +++ b/packages/shared/src/types/agent.ts @@ -1,3 +1,4 @@ +import type { AiConnectionLoginIntent } from "../ai-connections.js"; import type { AgentAdapterType, PauseReason, @@ -22,7 +23,9 @@ export interface AgentPermissions extends Record { authorizationPolicy?: TrustAuthorizationPolicy; } -export type AgentRuntimeConfig = Record; +export type AgentRuntimeConfig = Record & { + aiConnection?: import("../ai-connections.js").AiConnectionBinding; +}; export type AgentInstructionsBundleMode = "managed" | "external"; @@ -197,6 +200,7 @@ export interface CodexAccountBindingClaim { // The owner read of a login session. It adds the one-time prompt to the public // response. Only the owner principal that started the session reads this shape. export interface AdapterAuthSessionOwnerResponse extends AdapterAuthSessionResponse { + aiConnection?: AiConnectionLoginIntent; prompt: AdapterAuthSessionPrompt | null; codexAccountBinding?: CodexAccountBindingClaim | null; } @@ -204,6 +208,7 @@ export interface AdapterAuthSessionOwnerResponse extends AdapterAuthSessionRespo // The request that starts a login session for one adapter in one environment. // The owner principal comes from the authenticated caller, not from this body. export interface StartAdapterAuthSessionRequest { + aiConnection?: AiConnectionLoginIntent; environmentId: string; adapterType: AgentAdapterType; ttlSeconds?: number; @@ -278,6 +283,7 @@ export interface ClaudeSetupTokenSessionResponse { // the session reads this shape. export interface ClaudeSetupTokenSessionOwnerResponse extends ClaudeSetupTokenSessionResponse { + aiConnection?: AiConnectionLoginIntent; panelMode: AdapterAuthPanelMode; prompt: ClaudeSetupTokenSessionPrompt | null; } diff --git a/packages/shared/src/types/app-definition.ts b/packages/shared/src/types/app-definition.ts index 9f4f10c3f1..b7b532a053 100644 --- a/packages/shared/src/types/app-definition.ts +++ b/packages/shared/src/types/app-definition.ts @@ -2,7 +2,7 @@ import type { ConnectionGrantKind, ToolConnectionOwnership, ToolConnectionPurpos export type AppCategory = "ai"|"analytics"|"commerce"|"communication"|"content"|"data"|"developer"|"productivity"|"other"; export type OAuthRedirectConstraints = "https-or-loopback-http"; export interface FieldDef { key:string; label:string; type:"text"|"password"|"textarea"|"datetime"|"select"|"checkbox"; required?:boolean; advanced?:boolean; hidden?:boolean; placeholder?:string; helperMd?:string; secret?:boolean; prefix?:string; defaultValue?:string|boolean; validation?:{pattern?:string;maxLength?:number}; options?:Array<{value:string;label:string}>; transport?:{location:"query"|"header";name:string;format?:"string"|"csv"|"boolean";omitFalse?:boolean} } -export interface ConnectionMethodDef { key:string; label?:string; purpose?:ToolConnectionPurpose; provider?:"slack"|"github"|"discord"|"microsoft-teams"|"telegram" | "agentmail"; transport:ToolConnectionTransport; auth:"oauth"|"api_key"|"none"; oauthStrategy?:"paperclip_cloud_connector"|"paperclip_id_connector"; connectorProfile?:string; capabilityProfile?:{key:string;label:string;description?:string}; grantKinds?:ConnectionGrantKind[]; ownershipModes:ToolConnectionOwnership[]; whenToUse:string; defaults?:{serverUrl?:string;serverUrlTemplate?:string;discoveryUrl?:string|null;serviceHost?:string;templateKey?:string;authorizationEndpoint?:string;tokenEndpoint?:string;metadataUrl?:string;scopesHint?:string[];oauthAuthorizationParams?:{access_type?:"offline";prompt?:"consent"};toolArgumentDefaults?:Record}; tenantFields?:FieldDef[]; extensionFields?:FieldDef[]; configRequirements?:{atLeastOneOf?:string[]}; credentialFields?:FieldDef[]; keyPlacement?:{location:"header"|"query"|"body_json"|"env";name:string;prefix?:string|null}; credentialSources?:{vercelConnect?:{services:string[];principalModes:VercelConnectPrincipalMode[];scopes:string[];header:{name:string;prefix?:string|null}}}; guidanceMd:string; consoleLinks?:{register?:string;keys?:string;settings?:string;docs?:string}; warnings?:string[]; variants?:Array<{key:string;label:string;whenToUse:string;tenantFields?:FieldDef[]}>; riskTier:"S1"|"S2"|"S3"|"S4"; requiredResourceFilters?:string[] } +export interface ConnectionMethodDef { ai?: import("../ai-connections.js").AiConnectionMetadata; key:string; label?:string; purpose?:ToolConnectionPurpose; provider?:"slack"|"github"|"discord"|"microsoft-teams"|"telegram"|"agentmail"|"imessage-photon"; transport:ToolConnectionTransport; auth:"oauth"|"api_key"|"none"; oauthStrategy?:"paperclip_cloud_connector"|"paperclip_id_connector"; connectorProfile?:string; capabilityProfile?:{key:string;label:string;description?:string}; grantKinds?:ConnectionGrantKind[]; ownershipModes:ToolConnectionOwnership[]; whenToUse:string; defaults?:{serverUrl?:string;serverUrlTemplate?:string;discoveryUrl?:string|null;serviceHost?:string;templateKey?:string;authorizationEndpoint?:string;tokenEndpoint?:string;metadataUrl?:string;scopesHint?:string[];oauthAuthorizationParams?:{access_type?:"offline";prompt?:"consent"};toolArgumentDefaults?:Record}; tenantFields?:FieldDef[]; extensionFields?:FieldDef[]; configRequirements?:{atLeastOneOf?:string[]}; credentialFields?:FieldDef[]; keyPlacement?:{location:"header"|"query"|"body_json"|"env";name:string;prefix?:string|null}; credentialSources?:{vercelConnect?:{services:string[];principalModes:VercelConnectPrincipalMode[];scopes:string[];header:{name:string;prefix?:string|null}}}; guidanceMd:string; consoleLinks?:{register?:string;keys?:string;settings?:string;docs?:string}; warnings?:string[]; variants?:Array<{key:string;label:string;whenToUse:string;tenantFields?:FieldDef[]}>; riskTier:"S1"|"S2"|"S3"|"S4"; requiredResourceFilters?:string[] } export interface AppDefinition { schemaVersion:1; slug:string; name:string; description:string; categories:AppCategory[]; featured?:boolean; branding:{logoUrl:string;darkLogoUrl?:string;backgroundColor?:string;accentColor?:string}; urlPatterns:string[]; docsUrl?:string; setupPrerequisite?:{title:string;description:string;steps?:string[];actionLabel:string;actionUrl:string}; redirectConstraints?:OAuthRedirectConstraints; methods:ConnectionMethodDef[]; suggestable?:boolean; availability?:{available:boolean;reason?:string;robotEmail?:string}; ownershipAvailability?:Partial> } export type SelfServeMcpAuthMode = diff --git a/packages/shared/src/types/chat-channels.ts b/packages/shared/src/types/chat-channels.ts index f71204a924..7c09cd06e9 100644 --- a/packages/shared/src/types/chat-channels.ts +++ b/packages/shared/src/types/chat-channels.ts @@ -6,6 +6,7 @@ export const CHAT_PROVIDERS = [ "microsoft-teams", "telegram", "agentmail", + "imessage-photon", ] as const; export type ChatProvider = (typeof CHAT_PROVIDERS)[number]; @@ -226,6 +227,7 @@ export interface ChatEndpoint { botUsername?: string | null; botLabel?: string | null; botAvatarUrl?: string | null; + photonAllocation?: "dedicated" | "shared"; allowDirectMessages: boolean; allowGroupChats: boolean; allowUnlinkedPeople: boolean; @@ -255,6 +257,7 @@ export interface ChatEndpointResource { enabled: boolean; createdAt: string; updatedAt: string; + participants?: string[]; } export interface ChatExternalPrincipal { @@ -467,6 +470,7 @@ export interface UpdateChatEndpointInput { export interface ConfigureChatEndpointInput { action: "configure" | "verify" | "pause" | "resume" | "reconnect" | "remove"; credentials?: Record; + photon?: PhotonChannelConfiguration; } export interface NormalizedChatEvent { @@ -503,3 +507,15 @@ export interface NormalizedChatEvent { }; raw: Record; } + +/** Safe Photon project inspection; credentials and line tokens are never serialized. */ +export interface PhotonProjectInspection { + projectId: string; + projectName: string; + allocation: "dedicated" | "shared"; + eligible: boolean; + lines: Array<{ lineId: string; phoneNumber: string; eligible: boolean; unavailableReason?: string }>; +} +export type PhotonChannelConfiguration = + | { allocation?: "dedicated"; projectId: string; lineId: string } + | { allocation: "shared"; projectId: string }; diff --git a/packages/shared/src/types/connection-intent.ts b/packages/shared/src/types/connection-intent.ts index 3264ab28fe..84515669fc 100644 --- a/packages/shared/src/types/connection-intent.ts +++ b/packages/shared/src/types/connection-intent.ts @@ -42,6 +42,12 @@ export interface ConnectionRequestResult { export type ConnectionIntentSetupConnection = Pick; export interface ConnectionIntentSetupOptions { + aiConnection?: import("../ai-connections.js").AiConnectionBinding; + /** Selected account, including an unavailable default. Reconnect must preserve its identity. */ + aiRepair?: { + connection: import("../ai-connections.js").AiManagedConnectionSummary; + canReconnect: boolean; + }; version: 1; interaction: ConnectionIntentInteraction; service: ConnectionSearchResultItem; diff --git a/packages/shared/src/types/issue.ts b/packages/shared/src/types/issue.ts index 2a2007ae45..348ce8ed88 100644 --- a/packages/shared/src/types/issue.ts +++ b/packages/shared/src/types/issue.ts @@ -1057,6 +1057,8 @@ export interface IssueCommentMetadataSection { export interface IssueCommentMetadata { version: 1; + /** Inbound channel attribution; never an authorization input. */ + sourceChannel?: "imessage-photon"; sourceRunId?: string | null; sourceIdentityContextId?: string | null; authorizationReason?: string | null; @@ -1329,6 +1331,8 @@ export type ConnectionIntentPhase = "requested" | "authorizing" | "needs_retry"; */ export interface ConnectionIntentPayload { version: 1; + /** Runtime authentication requests cannot be satisfied by tool credentials. */ + purpose?: "ai"; serviceSlug: string; serviceName: string; serviceLogoUrl?: string | null; diff --git a/packages/shared/src/types/tool-access.ts b/packages/shared/src/types/tool-access.ts index 1e7e411f14..db50522bca 100644 --- a/packages/shared/src/types/tool-access.ts +++ b/packages/shared/src/types/tool-access.ts @@ -68,8 +68,8 @@ export type { export type ToolActorType = "agent" | "user" | "system" | "plugin"; export type ToolConnectionTransport = - "mcp_remote" | "rest_api" | "local_stdio" | "chat_sdk"; -export type ToolConnectionPurpose = "tool" | "channel"; + "mcp_remote" | "rest_api" | "local_stdio" | "chat_sdk" | "runtime_auth"; +export type ToolConnectionPurpose = "tool" | "channel" | "ai"; export type ToolConnectionAuthKind = "oauth" | "api_key" | "none"; export type ToolConnectionOwnership = "platform_shared" | "platform_provisioned" | "customer" | "dcr"; diff --git a/packages/shared/src/validators/adapter-auth-session.ts b/packages/shared/src/validators/adapter-auth-session.ts index aed8b2aba0..b98b6c4c70 100644 --- a/packages/shared/src/validators/adapter-auth-session.ts +++ b/packages/shared/src/validators/adapter-auth-session.ts @@ -1,3 +1,4 @@ +import { aiConnectionLoginIntentSchema } from "../ai-connections.js"; import { z } from "zod"; import { AGENT_ADAPTER_TYPES } from "../constants.js"; import { ADAPTER_AUTH_SESSION_STATUSES } from "../types/agent.js"; @@ -35,11 +36,13 @@ export type AdapterAuthSessionPrompt = z.infer; export const startAdapterAuthSessionRequestSchema = z.object({ + aiConnection: aiConnectionLoginIntentSchema.optional(), environmentId: z.string().guid(), adapterType: z.enum(AGENT_ADAPTER_TYPES), ttlSeconds: z.number().int().min(60).max(24 * 60 * 60).optional(), diff --git a/packages/shared/src/validators/agent.ts b/packages/shared/src/validators/agent.ts index fbff34fa72..dfe8c963e4 100644 --- a/packages/shared/src/validators/agent.ts +++ b/packages/shared/src/validators/agent.ts @@ -1,3 +1,4 @@ +import { aiConnectionBindingSchema } from "../ai-connections.js"; import { z } from "zod"; import { AGENT_ICON_NAMES, @@ -60,6 +61,7 @@ export const createAgentInstructionsBundleSchema = z.object({ }); export const agentRuntimeConfigSchema = z.object({ + aiConnection: aiConnectionBindingSchema.optional(), debug: z.object({ providerTrace: z.literal("raw").optional(), }).strict().optional(), @@ -242,6 +244,7 @@ export const resetAgentSessionSchema = z.object({ export type ResetAgentSession = z.infer; export const testAdapterEnvironmentSchema = z.object({ + aiConnection: aiConnectionBindingSchema.optional(), /** Saved agent whose redacted environment entries are restored for this probe. */ agentId: z.string().guid().optional(), /** One-shot provider keys for a probe. Never persist these in agent config. */ diff --git a/packages/shared/src/validators/app-definition.ts b/packages/shared/src/validators/app-definition.ts index 203756e667..9be04386da 100644 --- a/packages/shared/src/validators/app-definition.ts +++ b/packages/shared/src/validators/app-definition.ts @@ -1,10 +1,11 @@ import { z } from "zod"; +import { AI_CONNECTION_CAPABILITIES, aiConnectionMetadataSchema } from "../ai-connections.js"; import { connectionGrantKindSchema, toolConnectionOwnershipSchema, toolConnectionPurposeSchema, toolConnectionTransportSchema } from "./tool-access.js"; const appBrandAssetUrlSchema=z.string().refine((value)=>{ if(/^\/brands\/apps\/[a-z0-9][a-z0-9._-]*\.(?:svg|png)$/i.test(value))return true; try{return new URL(value).protocol==="https:";}catch{return false;} },{message:"Brand assets must be HTTPS URLs or local /brands/apps SVG/PNG paths"}); const field=z.object({key:z.string().min(1),label:z.string().min(1),type:z.enum(["text","password","textarea","datetime","select","checkbox"]),required:z.boolean().optional(),advanced:z.boolean().optional(),hidden:z.boolean().optional(),placeholder:z.string().optional(),helperMd:z.string().optional(),secret:z.boolean().optional(),prefix:z.string().optional(),defaultValue:z.union([z.string(),z.boolean()]).optional(),validation:z.object({pattern:z.string().optional(),maxLength:z.number().int().positive().optional()}).optional(),options:z.array(z.object({value:z.string(),label:z.string()})).optional(),transport:z.object({location:z.enum(["query","header"]),name:z.string().min(1),format:z.enum(["string","csv","boolean"]).optional(),omitFalse:z.boolean().optional()}).optional()}).superRefine((v,c)=>{if(v.required&&v.type!=="checkbox"&&!v.placeholder)c.addIssue({code:"custom",message:"Required fields need placeholders",path:["placeholder"]});if(v.type==="select"&&(!v.options||v.options.length===0))c.addIssue({code:"custom",message:"Select fields need options",path:["options"]});if(v.hidden&&v.defaultValue===undefined)c.addIssue({code:"custom",message:"Hidden fields need defaults",path:["defaultValue"]})}); -export const connectionMethodDefSchema=z.object({key:z.string().min(1),label:z.string().min(1).optional(),purpose:toolConnectionPurposeSchema.optional(),provider:z.enum(["slack","github","discord","microsoft-teams","telegram","agentmail"]).optional(),transport:toolConnectionTransportSchema,auth:z.enum(["oauth","api_key","none"]),oauthStrategy:z.enum(["paperclip_cloud_connector","paperclip_id_connector"]).optional(),connectorProfile:z.string().regex(/^[a-z0-9]+(?:[.-][a-z0-9]+)*$/).optional(),capabilityProfile:z.object({key:z.string().min(1),label:z.string().min(1),description:z.string().min(1).optional()}).optional(),grantKinds:z.array(connectionGrantKindSchema).min(1).optional(),ownershipModes:z.array(toolConnectionOwnershipSchema).min(1),whenToUse:z.string().min(1),defaults:z.object({serverUrl:z.string().url().optional(),serverUrlTemplate:z.string().regex(/^https:\/\//).optional(),discoveryUrl:z.string().url().nullable().optional(),serviceHost:z.string().optional(),templateKey:z.string().optional(),authorizationEndpoint:z.string().url().optional(),tokenEndpoint:z.string().url().optional(),metadataUrl:z.string().url().optional(),scopesHint:z.array(z.string()).optional(),oauthAuthorizationParams:z.object({access_type:z.literal("offline").optional(),prompt:z.literal("consent").optional()}).optional(),toolArgumentDefaults:z.record(z.string(),z.unknown()).optional()}).optional(),tenantFields:z.array(field).optional(),extensionFields:z.array(field).optional(),configRequirements:z.object({atLeastOneOf:z.array(z.string().min(1)).min(1).optional()}).optional(),credentialFields:z.array(field).optional(),keyPlacement:z.object({location:z.enum(["header","query","body_json","env"]),name:z.string().min(1),prefix:z.string().nullable().optional()}).optional(),credentialSources:z.object({vercelConnect:z.object({services:z.array(z.string().min(1)).min(1),principalModes:z.array(z.enum(["app","user"])).min(1),scopes:z.array(z.string().min(1)).min(1),header:z.object({name:z.string().min(1),prefix:z.string().nullable().optional()})}).optional()}).optional(),guidanceMd:z.string().min(1),consoleLinks:z.object({register:z.string().url().optional(),keys:z.string().url().optional(),settings:z.string().url().optional(),docs:z.string().url().optional()}).optional(),warnings:z.array(z.string()).optional(),variants:z.array(z.object({key:z.string(),label:z.string(),whenToUse:z.string(),tenantFields:z.array(field).optional()})).optional(),riskTier:z.enum(["S1","S2","S3","S4"]),requiredResourceFilters:z.array(z.string()).optional()}).superRefine((v,c)=>{const purpose=v.purpose??"tool";if(v.transport==="chat_sdk"&&purpose!=="channel")c.addIssue({code:"custom",message:"Chat SDK methods must be channel connections",path:["purpose"]});if(purpose==="channel"&&v.transport!=="chat_sdk"&&!(v.provider==="agentmail"&&v.transport==="rest_api"))c.addIssue({code:"custom",message:"Channel connections must use the Chat SDK transport",path:["transport"]});if(purpose==="channel"&&!v.provider)c.addIssue({code:"custom",message:"Channel connections require a chat provider",path:["provider"]});if(v.auth==="api_key"&&!v.keyPlacement&&purpose!=="channel")c.addIssue({code:"custom",message:"API-key tool methods require keyPlacement",path:["keyPlacement"]});if(v.oauthStrategy&&v.auth!=="oauth")c.addIssue({code:"custom",message:"OAuth strategies require OAuth auth",path:["oauthStrategy"]});if(v.oauthStrategy&&!v.connectorProfile)c.addIssue({code:"custom",message:"Paperclip Cloud connector methods require connectorProfile",path:["connectorProfile"]});if(v.connectorProfile&&!v.oauthStrategy)c.addIssue({code:"custom",message:"connectorProfile requires a Paperclip Cloud OAuth strategy",path:["connectorProfile"]});if(v.credentialSources?.vercelConnect&&(v.transport!=="mcp_remote"||v.auth==="none"))c.addIssue({code:"custom",message:"Vercel Connect requires an authenticated remote MCP method",path:["credentialSources","vercelConnect"]});const keys=new Set([...(v.tenantFields??[]),...(v.extensionFields??[])].map((entry)=>entry.key));for(const key of v.configRequirements?.atLeastOneOf??[])if(!keys.has(key))c.addIssue({code:"custom",message:"Config requirement references an unknown field",path:["configRequirements","atLeastOneOf"]});if(v.defaults?.serverUrl&&v.defaults.serverUrlTemplate)c.addIssue({code:"custom",message:"Use either serverUrl or serverUrlTemplate",path:["defaults"]});for(const placeholder of v.defaults?.serverUrlTemplate?.matchAll(/\{([a-zA-Z0-9_-]+)\}/g)??[])if(!keys.has(placeholder[1]))c.addIssue({code:"custom",message:"Server URL template references an unknown field",path:["defaults","serverUrlTemplate"]})}); +export const connectionMethodDefSchema=z.object({key:z.string().min(1),label:z.string().min(1).optional(),purpose:z.union([toolConnectionPurposeSchema,z.literal("ai")]).optional(),provider:z.enum(["slack","github","discord","microsoft-teams","telegram","agentmail","imessage-photon"]).optional(),transport:z.union([toolConnectionTransportSchema,z.literal("runtime_auth")]),ai:aiConnectionMetadataSchema.optional(),auth:z.enum(["oauth","api_key","none"]),oauthStrategy:z.enum(["paperclip_cloud_connector","paperclip_id_connector"]).optional(),connectorProfile:z.string().regex(/^[a-z0-9]+(?:[.-][a-z0-9]+)*$/).optional(),capabilityProfile:z.object({key:z.string().min(1),label:z.string().min(1),description:z.string().min(1).optional()}).optional(),grantKinds:z.array(connectionGrantKindSchema).min(1).optional(),ownershipModes:z.array(toolConnectionOwnershipSchema).min(1),whenToUse:z.string().min(1),defaults:z.object({serverUrl:z.string().url().optional(),serverUrlTemplate:z.string().regex(/^https:\/\//).optional(),discoveryUrl:z.string().url().nullable().optional(),serviceHost:z.string().optional(),templateKey:z.string().optional(),authorizationEndpoint:z.string().url().optional(),tokenEndpoint:z.string().url().optional(),metadataUrl:z.string().url().optional(),scopesHint:z.array(z.string()).optional(),oauthAuthorizationParams:z.object({access_type:z.literal("offline").optional(),prompt:z.literal("consent").optional()}).optional(),toolArgumentDefaults:z.record(z.string(),z.unknown()).optional()}).optional(),tenantFields:z.array(field).optional(),extensionFields:z.array(field).optional(),configRequirements:z.object({atLeastOneOf:z.array(z.string().min(1)).min(1).optional()}).optional(),credentialFields:z.array(field).optional(),keyPlacement:z.object({location:z.enum(["header","query","body_json","env"]),name:z.string().min(1),prefix:z.string().nullable().optional()}).optional(),credentialSources:z.object({vercelConnect:z.object({services:z.array(z.string().min(1)).min(1),principalModes:z.array(z.enum(["app","user"])).min(1),scopes:z.array(z.string().min(1)).min(1),header:z.object({name:z.string().min(1),prefix:z.string().nullable().optional()})}).optional()}).optional(),guidanceMd:z.string().min(1),consoleLinks:z.object({register:z.string().url().optional(),keys:z.string().url().optional(),settings:z.string().url().optional(),docs:z.string().url().optional()}).optional(),warnings:z.array(z.string()).optional(),variants:z.array(z.object({key:z.string(),label:z.string(),whenToUse:z.string(),tenantFields:z.array(field).optional()})).optional(),riskTier:z.enum(["S1","S2","S3","S4"]),requiredResourceFilters:z.array(z.string()).optional()}).superRefine((v,c)=>{if((v.transport==="runtime_auth")!==Boolean(v.ai))c.addIssue({code:"custom",message:"Runtime authentication requires AI metadata and AI metadata requires runtime_auth",path:["ai"]});if(v.ai&&!AI_CONNECTION_CAPABILITIES[v.ai.provider].methods[v.ai.method])c.addIssue({code:"custom",message:"Unsupported AI sign-in method",path:["ai","method"]});if(v.ai&&v.auth!==(v.ai.method==="subscription"?"oauth":"api_key"))c.addIssue({code:"custom",message:"AI sign-in method must match authentication",path:["auth"]});const purpose=v.purpose??"tool";if((purpose==="ai")!==(v.transport==="runtime_auth"))c.addIssue({code:"custom",message:"AI methods require runtime_auth and runtime_auth requires AI purpose",path:["purpose"]});if(v.transport==="chat_sdk"&&purpose!=="channel")c.addIssue({code:"custom",message:"Chat SDK methods must be channel connections",path:["purpose"]});if(purpose==="channel"&&v.transport!=="chat_sdk"&&!(v.provider==="agentmail"&&v.transport==="rest_api"))c.addIssue({code:"custom",message:"Channel connections must use the Chat SDK transport",path:["transport"]});if(purpose==="channel"&&!v.provider)c.addIssue({code:"custom",message:"Channel connections require a chat provider",path:["provider"]});if(v.auth==="api_key"&&!v.keyPlacement&&purpose!=="channel")c.addIssue({code:"custom",message:"API-key tool methods require keyPlacement",path:["keyPlacement"]});if(v.oauthStrategy&&v.auth!=="oauth")c.addIssue({code:"custom",message:"OAuth strategies require OAuth auth",path:["oauthStrategy"]});if(v.oauthStrategy&&!v.connectorProfile)c.addIssue({code:"custom",message:"Paperclip Cloud connector methods require connectorProfile",path:["connectorProfile"]});if(v.connectorProfile&&!v.oauthStrategy)c.addIssue({code:"custom",message:"connectorProfile requires a Paperclip Cloud OAuth strategy",path:["connectorProfile"]});if(v.credentialSources?.vercelConnect&&(v.transport!=="mcp_remote"||v.auth==="none"))c.addIssue({code:"custom",message:"Vercel Connect requires an authenticated remote MCP method",path:["credentialSources","vercelConnect"]});const keys=new Set([...(v.tenantFields??[]),...(v.extensionFields??[])].map((entry)=>entry.key));for(const key of v.configRequirements?.atLeastOneOf??[])if(!keys.has(key))c.addIssue({code:"custom",message:"Config requirement references an unknown field",path:["configRequirements","atLeastOneOf"]});if(v.defaults?.serverUrl&&v.defaults.serverUrlTemplate)c.addIssue({code:"custom",message:"Use either serverUrl or serverUrlTemplate",path:["defaults"]});for(const placeholder of v.defaults?.serverUrlTemplate?.matchAll(/\{([a-zA-Z0-9_-]+)\}/g)??[])if(!keys.has(placeholder[1]))c.addIssue({code:"custom",message:"Server URL template references an unknown field",path:["defaults","serverUrlTemplate"]})}); export const appDefinitionSchema=z.object({schemaVersion:z.literal(1),slug:z.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/),name:z.string().min(1),description:z.string().min(1),categories:z.array(z.enum(["ai","analytics","commerce","communication","content","data","developer","productivity","other"])).min(1),featured:z.boolean().optional(),branding:z.object({logoUrl:appBrandAssetUrlSchema,darkLogoUrl:appBrandAssetUrlSchema.optional(),backgroundColor:z.string().optional(),accentColor:z.string().optional()}),urlPatterns:z.array(z.string()),docsUrl:z.string().url().optional(),setupPrerequisite:z.object({title:z.string().min(1),description:z.string().min(1),steps:z.array(z.string().min(1)).min(1).optional(),actionLabel:z.string().min(1),actionUrl:z.string().url()} ).optional(),redirectConstraints:z.enum(["https-or-loopback-http"]).optional(),methods:z.array(connectionMethodDefSchema).min(1),suggestable:z.boolean().optional(),availability:z.object({available:z.boolean(),reason:z.string().optional(),robotEmail:z.string().optional()}).optional(),ownershipAvailability:z.object({platform_shared:z.boolean().optional(),platform_provisioned:z.boolean().optional(),customer:z.boolean().optional(),dcr:z.boolean().optional()}).optional()}); export const appDefinitionsSchema=z.array(appDefinitionSchema).superRefine((v,c)=>{const s=new Set();v.forEach((a,i)=>{if(s.has(a.slug))c.addIssue({code:"custom",message:"Duplicate slug",path:[i,"slug"]});s.add(a.slug)})}); diff --git a/packages/shared/src/validators/chat-channels.ts b/packages/shared/src/validators/chat-channels.ts index 099411911b..4be41d5d61 100644 --- a/packages/shared/src/validators/chat-channels.ts +++ b/packages/shared/src/validators/chat-channels.ts @@ -87,6 +87,17 @@ export const updateChatEndpointSchema = z message: "At least one chat endpoint field is required", }); +export const photonProjectIdSchema = z.string().trim().min(1).max(128).regex(/^[a-zA-Z0-9_-]+$/); +export const photonLineIdSchema = z.string().trim().min(1).max(63).regex(/^[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?$/); +export const photonChannelConfigurationSchema = z.union([ + z.object({ allocation: z.literal("dedicated").default("dedicated"), projectId: photonProjectIdSchema, lineId: photonLineIdSchema }).strict(), + z.object({ allocation: z.literal("shared"), projectId: photonProjectIdSchema }).strict(), +]); +export const inspectPhotonProjectSchema = z.object({ + projectId: photonProjectIdSchema, + projectSecret: z.string().min(1).max(4096), +}).strict(); + export const configureChatEndpointSchema = z .object({ action: z.enum([ @@ -98,11 +109,12 @@ export const configureChatEndpointSchema = z "remove", ]), credentials: chatEndpointCredentialsSchema.optional(), + photon: photonChannelConfigurationSchema.optional(), }) .strict() .superRefine((value, ctx) => { if ( - value.credentials && + (value.credentials || value.photon) && value.action !== "configure" && value.action !== "reconnect" ) { diff --git a/packages/shared/src/validators/claude-setup-token-session.ts b/packages/shared/src/validators/claude-setup-token-session.ts index 663782e8b7..581303dd7a 100644 --- a/packages/shared/src/validators/claude-setup-token-session.ts +++ b/packages/shared/src/validators/claude-setup-token-session.ts @@ -1,3 +1,4 @@ +import { aiConnectionLoginIntentSchema } from "../ai-connections.js"; import { z } from "zod"; import { AGENT_ADAPTER_TYPES } from "../constants.js"; import { ADAPTER_AUTH_PANEL_MODES, SETUP_TOKEN_TRANSPORT_ADVISORY_CODE } from "../types/agent.js"; @@ -43,6 +44,7 @@ export type ClaudeSetupTokenOverwrite = // runtime does not support a caller-supplied session length, so the schema // exposes no `ttlSeconds` field; a legacy `ttlSeconds` fails the strict parse. export const startClaudeSetupTokenSessionRequestSchema = z.object({ + aiConnection: aiConnectionLoginIntentSchema.optional(), environmentId: z.string().guid(), adapterType: z.enum(AGENT_ADAPTER_TYPES), overwrite: claudeSetupTokenOverwriteSchema.optional(), @@ -87,6 +89,7 @@ export const claudeSetupTokenSessionOwnerResponseSchema = claudeSetupTokenSessionResponseSchema.extend({ panelMode: adapterAuthPanelModeSchema, prompt: claudeSetupTokenSessionPromptSchema.nullable(), + aiConnection: aiConnectionLoginIntentSchema.optional(), }).strict(); export type ClaudeSetupTokenSessionOwnerResponse = z.infer; diff --git a/packages/shared/src/validators/issue.ts b/packages/shared/src/validators/issue.ts index 1a556c2988..37cd386f6f 100644 --- a/packages/shared/src/validators/issue.ts +++ b/packages/shared/src/validators/issue.ts @@ -1000,6 +1000,7 @@ export const issueCommentMetadataSectionSchema = z export const issueCommentMetadataSchema = z .object({ version: z.literal(1), + sourceChannel: z.literal("imessage-photon").optional(), sourceRunId: z.string().guid().nullable().optional(), authorizationReason: z .string() @@ -1073,6 +1074,7 @@ const connectionIntentBrandAssetSchema = z export const connectionIntentPayloadSchema = z .object({ + purpose: z.literal("ai").optional(), version: z.literal(1), serviceSlug: z.string().trim().min(1).max(120), serviceName: z.string().trim().min(1).max(160), diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8e0aaab62d..855e9cff4c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,6 +10,8 @@ overrides: rollup: '>=4.59.0' react: ^19.2.8 react-dom: ^19.2.8 + '@codemirror/state': ^6.7.2 + '@codemirror/view': ^6.43.11 patchedDependencies: '@agentclientprotocol/claude-agent-acp@0.73.0': @@ -618,11 +620,11 @@ importers: specifier: ^6.12.4 version: 6.12.4 '@codemirror/state': - specifier: ^6.7.1 - version: 6.7.1 + specifier: ^6.7.2 + version: 6.7.2 '@codemirror/view': - specifier: ^6.43.9 - version: 6.43.9 + specifier: ^6.43.11 + version: 6.43.11 '@lezer/highlight': specifier: ^1.2.1 version: 1.2.3 @@ -883,6 +885,9 @@ importers: '@discordjs/ws': specifier: 1.2.3 version: 1.2.3(patch_hash=vbylpnnxt5av2zdnuqmf6hjyjy) + '@grpc/grpc-js': + specifier: 1.14.4 + version: 1.14.4 '@opentelemetry/api': specifier: ^1.9.0 version: 1.9.1 @@ -934,6 +939,9 @@ importers: '@paperclipai/skills-catalog': specifier: workspace:* version: link:../packages/skills-catalog + '@photon-ai/advanced-imessage': + specifier: 2.1.0 + version: 2.1.0(@grpc/grpc-js@1.14.4)(nice-grpc-common@2.0.4)(nice-grpc@2.1.17) '@vercel/connect': specifier: 0.6.1 version: 0.6.1(@chat-adapter/slack@4.39.0(patch_hash=226jj24akljccsfbw7bcxujz4u)(zod@4.4.3))(better-auth@1.7.2(@opentelemetry/api@1.9.1)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(kysely@0.29.5)(pg@8.18.0)(postgres@3.4.9))(pg@8.18.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vitest@4.1.11(@opentelemetry/api@1.9.1)(@types/node@24.13.3)(esbuild@0.28.2)(jiti@2.7.0)(jsdom@30.0.1(@noble/hashes@2.4.0))(tsx@4.23.12))) @@ -973,12 +981,21 @@ importers: express: specifier: ^5.1.0 version: 5.2.1 + heif2jpeg: + specifier: 0.1.6 + version: 0.1.6 jsdom: specifier: ^30.0.1 version: 30.0.1(@noble/hashes@2.4.0) multer: specifier: ^2.2.0 version: 2.2.0 + nice-grpc: + specifier: 2.1.17 + version: 2.1.17 + nice-grpc-common: + specifier: 2.0.4 + version: 2.0.4 open: specifier: ^11.0.1 version: 11.0.1 @@ -1664,6 +1681,9 @@ packages: '@bufbuild/protobuf@1.10.0': resolution: {integrity: sha512-QDdVFLoN93Zjg36NoQPZfsVH9tZew7wKDKyV5qRdj8ntT4wQCOradQjRaTdwMhWUYsgKsvCINKKm87FdEk96Ag==} + '@bufbuild/protobuf@2.15.0': + resolution: {integrity: sha512-DAheWUkVr/SJTWCc+lg9dhY0eN4SaWlf4+bG1KzHeXbnqt0AfB/NX0Z+VunGlM1ki1B4zVvye27MpKh/svySUA==} + '@chat-adapter/discord@4.39.0': resolution: {integrity: sha512-9IS4HF0Jw+Ucsm3f7XaXGCGb22Swb/r6FGaHoSDYxOEraQ88pFH5S7xc8//C+pCnqhX7p8QwLB/dYaNxDnl9pw==} engines: {node: '>=20'} @@ -1795,18 +1815,12 @@ packages: '@codemirror/search@6.6.0': resolution: {integrity: sha512-koFuNXcDvyyotWcgOnZGmY7LZqEOXZaaxD/j6n18TCLx2/9HieZJ5H6hs1g8FiRxBD0DNfs0nXn17g872RmYdw==} - '@codemirror/state@6.7.1': - resolution: {integrity: sha512-9QzNDgE4EYDnAHfrTlR2lwiPciiOymLtwKK+8yHQzCc7GXhAP9xdEbEJFy2IWB1j9UGUl9BsgMmTo/ImA02T7A==} - '@codemirror/state@6.7.2': resolution: {integrity: sha512-U3RiPX62Wl/Gx4ftQ7UxLlloSfsFTQqKa+7vFBYteZGCzkp6oBqcsD7iSwniWRGobCAmDcwZSY+Six3+3ztdfg==} '@codemirror/view@6.43.11': resolution: {integrity: sha512-2+esucbQX6wB2JYi1eDvdCPFTA31BN8oSy6xCmk3G6CloV11yOvEjYk+gH7kLrP0MuHG94E8WDhjs5oMiu3+Wg==} - '@codemirror/view@6.43.9': - resolution: {integrity: sha512-sTuUzTpPMFebRhg6dawChoKKgndIwfjmJgKVxBefPElcU2NwQ6AFroupk0SFqEerQyZOGRfDNnSN8Dw/lMAsXw==} - '@connectrpc/connect-node@1.7.0': resolution: {integrity: sha512-6vaPIkG/NyhxlYgytLoR9KYbPhczEboFB2OYWkA9qvUz1K7efXfeGrlRxoLtpa+r8VxyIOw73w5ktNe743nD+A==} engines: {node: '>=16.0.0'} @@ -2506,6 +2520,51 @@ packages: '@floating-ui/utils@0.2.12': resolution: {integrity: sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==} + '@grpc/grpc-js@1.14.4': + resolution: {integrity: sha512-k9Dj3DV/itK9D06Y8f190Qgop7/Ui+D0njFV3LHMPwPT75DpXLQohE9Wmz0QElrJnzsjB7KPWiKJbOl7IPDArQ==} + engines: {node: '>=12.10.0'} + + '@grpc/proto-loader@0.8.1': + resolution: {integrity: sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg==} + engines: {node: '>=6'} + hasBin: true + + '@heif2jpeg/heif2jpeg-darwin-arm64@0.1.6': + resolution: {integrity: sha512-HSXg4gGIb/RUwdUyRfXMopxvs8JpTLexW5+XLV3MqNZ8bMjUINv8aAigBECZ7It8dFWfcpui236S+z/LLMMFHQ==} + engines: {node: '>= 18'} + cpu: [arm64] + os: [darwin] + + '@heif2jpeg/heif2jpeg-darwin-x64@0.1.6': + resolution: {integrity: sha512-2Rno3RuhB5jK61RoMThoD9XNG+p997xOs9qN+qAk4vlr8IVFvAamunWvmtumHiJOWQ97mL7WuvpTa4PGDadqcQ==} + engines: {node: '>= 18'} + cpu: [x64] + os: [darwin] + + '@heif2jpeg/heif2jpeg-linux-arm64-gnu@0.1.6': + resolution: {integrity: sha512-myeXEKyYG39tPz7Tir526LozlRKk8a70zdpiBoEIZs1vxft3NAyj7OiR2SHbEQVOuRmcdOtJ6wxswvbafzOtAw==} + engines: {node: '>= 18'} + cpu: [arm64] + os: [linux] + + '@heif2jpeg/heif2jpeg-linux-x64-gnu@0.1.6': + resolution: {integrity: sha512-fvxLcCxbmXNHEvGpnKjVZmO3Wx1ew/e2+LvOOKW1vHbH5/5E7xdgYpbJVGcVa4s4nneKbybhNXDfaMdcZYqRgg==} + engines: {node: '>= 18'} + cpu: [x64] + os: [linux] + + '@heif2jpeg/heif2jpeg-win32-arm64-msvc@0.1.6': + resolution: {integrity: sha512-WWh/rOngHL+q27Y8TXIwsBH111gBf5Z7kBc14LZg+LoD/EnK1KzXx5NcguIyhuKzAEQJk+BwTIqfP7HeFWCrmw==} + engines: {node: '>= 18'} + cpu: [arm64] + os: [win32] + + '@heif2jpeg/heif2jpeg-win32-x64-msvc@0.1.6': + resolution: {integrity: sha512-Op3rpPzCwUxXHbzJD9hHNFBlV2nLr2/j+oNg5v0v4utKhLJ5GZg7NbfMJa07T1MkYMRs6lS60QhY/IWwWbSlFQ==} + engines: {node: '>= 18'} + cpu: [x64] + os: [win32] + '@hono/node-server@2.1.0': resolution: {integrity: sha512-XovyyCCnBzW+zKu+z/zq8hwNs4KOR5rEMAOxo2f40Q5xoOI37IMm6MIg2COOUtUApo0i6850MTBKH2u4QLGIqg==} engines: {node: '>=20'} @@ -2693,6 +2752,9 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@js-sdsl/ordered-map@4.4.2': + resolution: {integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==} + '@lexical/a11y@0.48.0': resolution: {integrity: sha512-18W4ehyipkUim4YVoDZitoH63Om3j6iCN4c84zdqE9RgkWf/PE4rvI/8BHTm6Ni7NkVE14nimXgkpaP5ok15zA==} peerDependencies: @@ -2939,9 +3001,6 @@ packages: '@lezer/yaml@1.0.4': resolution: {integrity: sha512-2lrrHqxalACEbxIbsjhqGpSW8kWpUKuY6RHgnSAFZa6qK62wvnPxA8hGOwOoDbwHcOFs5M4o27mjGu+P7TvBmw==} - '@marijn/find-cluster-break@1.0.3': - resolution: {integrity: sha512-FY+MKLBoTsLNJF/eLWaOsXGdz6uh3Iu1axjPf6TUq92IYumcTcXWHoS747JARLkcdlJ/Waiaxc5wQfFO8jC6NA==} - '@marijn/find-cluster-break@1.0.4': resolution: {integrity: sha512-Wy0V7+SGUjnF9/TkiM1hKVDPj7jKXduPNboMVtHTA8dySMURWqfg/JZ9E2Sq8JgSJmkl7k7Qe9FLeMSrSraWmQ==} @@ -3381,6 +3440,21 @@ packages: '@paralleldrive/cuid2@2.3.1': resolution: {integrity: sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==} + '@photon-ai/advanced-imessage@2.1.0': + resolution: {integrity: sha512-KT/aCBWcq667alyKe8DclqDUa/0fUuW2ZYDCxfIKuQBm08U4JcNG56fCdNUd69MFkKkzZgW35j59EYsx5DnLcQ==} + engines: {node: '>=18.17'} + peerDependencies: + '@grpc/grpc-js': ^1.12.6 + nice-grpc: ^2.1.11 + nice-grpc-common: ^2.0.2 + peerDependenciesMeta: + '@grpc/grpc-js': + optional: true + nice-grpc: + optional: true + nice-grpc-common: + optional: true + '@pierre/diffs@1.3.6': resolution: {integrity: sha512-a3woaW2QHy78JDxPJK0OJzwZUN4xoQLLIS/pceO8X6+L8gA5D682mP7/w3YxxEVRPXOaoe/p/RJ5Oj/3nrEzew==} peerDependencies: @@ -3426,6 +3500,33 @@ packages: '@preact/signals-core@1.14.4': resolution: {integrity: sha512-HNB6HYeYKhQbJ1aKl+YRjrS4+QWHLKX6qKoUsfS/m0vqzsVaEBiZiaKbG/e+NKk2ch5ALQr/ihWaMHxiCuuWHA==} + '@protobufjs/aspromise@1.1.2': + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + + '@protobufjs/base64@1.1.2': + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + + '@protobufjs/codegen@2.0.5': + resolution: {integrity: sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==} + + '@protobufjs/eventemitter@1.1.1': + resolution: {integrity: sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==} + + '@protobufjs/fetch@1.1.1': + resolution: {integrity: sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==} + + '@protobufjs/float@1.0.2': + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + + '@protobufjs/path@1.1.2': + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + + '@protobufjs/pool@1.1.0': + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + + '@protobufjs/utf8@1.1.2': + resolution: {integrity: sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==} + '@radix-ui/colors@3.0.0': resolution: {integrity: sha512-FUOsGBkHrYJwCSEtWRCIfQbZG7q1e6DgxCIOe1SUQzDe/7rXXeA47s8yCn6fuTNQAj1Zq4oTFi9Yjp3wzElcxg==} @@ -5363,6 +5464,9 @@ packages: '@xterm/xterm@6.0.0': resolution: {integrity: sha512-TQwDdQGtwwDt+2cgKDLn0IRaSxYu1tSUjgKarSDkUM0ZNiSRXFpjxEsvc/Zgc5kq5omJ+V0a8/kIM2WD3sMOYg==} + abort-controller-x@0.5.0: + resolution: {integrity: sha512-yTt9CI0x+nRfX6BFMenEGP8ooPvErGH6AbFz20C2IeOLIlDsrw/VHpgne3GsCEuTA410IiFiaLVFKmgM4bKEPQ==} + accepts@2.0.0: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} @@ -5751,6 +5855,10 @@ packages: classnames@2.5.1: resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} @@ -5759,8 +5867,8 @@ packages: resolution: {integrity: sha512-1prg2gv44sYfpHscP26uLT/ePrh0mlmVwMSoSd3zYKQ92Ab3jPRLzyCnpyOCQLJbK+YdNs4HvMRqMNYdy4pMhA==} peerDependencies: '@codemirror/language': ^6.0.0 - '@codemirror/state': ^6.0.0 - '@codemirror/view': ^6.0.0 + '@codemirror/state': ^6.7.2 + '@codemirror/view': ^6.43.11 '@lezer/highlight': ^1.0.0 cmdk@1.1.1: @@ -6644,6 +6752,10 @@ packages: hast-util-whitespace@3.0.0: resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + heif2jpeg@0.1.6: + resolution: {integrity: sha512-pXgHIbFS6HDcGhcYHkTltdKG+WSEb7Xtk8NSzQf7tVz3Nw8JzXojqyOqyqRfMLYLbHzxTuLN3a4FZKFlTHzcRA==} + engines: {node: '>= 18'} + help-me@5.0.0: resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==} @@ -7062,6 +7174,9 @@ packages: lodash-es@4.18.1: resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==} + lodash.camelcase@4.3.0: + resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + lodash.clonedeep@4.5.0: resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==} @@ -7092,6 +7207,9 @@ packages: lodash@4.18.1: resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} @@ -7438,6 +7556,12 @@ packages: resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} engines: {node: '>= 0.6'} + nice-grpc-common@2.0.4: + resolution: {integrity: sha512-gOEXlD6ShXZMZ8k+49wm/bA2j1+3IKbEFV9WYREJcvZV4kM5L1KkILYTX9VYBzZF2OuYCe1wp8c82bQkvR0fHw==} + + nice-grpc@2.1.17: + resolution: {integrity: sha512-pu9xYPlWSeqoYQOCqb2ftQqZi/P2DaI70PSuwnxvoyIRiilaOVtktyPe6LmuXegWB4Ic1sPuDGCnCCASbwzK0w==} + node-domexception@1.0.0: resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} engines: {node: '>=10.5.0'} @@ -7760,6 +7884,10 @@ packages: property-information@7.2.0: resolution: {integrity: sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==} + protobufjs@7.6.6: + resolution: {integrity: sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg==} + engines: {node: '>=12.0.0'} + proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -7975,6 +8103,10 @@ packages: remend@1.3.1: resolution: {integrity: sha512-N3DiY5qbRPoa5vkxn1oDLMyXOVTeo6Hp+XOj6SIqJAYUgLS0Q587gILPMom/qm86AQ/ZrcOdwEIzCz8V3J0nxQ==} + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} @@ -8384,6 +8516,9 @@ packages: resolution: {integrity: sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg==} engines: {node: '>=6.10'} + ts-error@1.0.6: + resolution: {integrity: sha512-tLJxacIQUM82IR7JO1UUkKlYuUTmoY9HBJAmNWFzheSlDS5SPMcNIepejHJa4BpPQLAcbRhRf3GDJzyj6rbKvA==} + ts-mixer@6.0.4: resolution: {integrity: sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==} @@ -8785,12 +8920,24 @@ packages: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.3: + resolution: {integrity: sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==} + engines: {node: '>=12'} + yjs@13.6.32: resolution: {integrity: sha512-lfiJIIC4Xayt5ItynE407ehlE03pCjeOc4hkR4yxxvvNJ4kuiN25B0g+Qp8XagYz361LLL7DCzR5bvFJ81QKtQ==} engines: {node: '>=16.0.0', npm: '>=8.0.0'} @@ -9360,6 +9507,8 @@ snapshots: '@bufbuild/protobuf@1.10.0': {} + '@bufbuild/protobuf@2.15.0': {} + '@chat-adapter/discord@4.39.0(patch_hash=nywlhls7dd4npegrgax7jqdbxa)(zod@4.4.3)': dependencies: '@chat-adapter/shared': 4.39.0(zod@4.4.3) @@ -9453,22 +9602,22 @@ snapshots: '@codemirror/autocomplete@6.20.0': dependencies: '@codemirror/language': 6.12.4 - '@codemirror/state': 6.7.1 - '@codemirror/view': 6.43.9 + '@codemirror/state': 6.7.2 + '@codemirror/view': 6.43.11 '@lezer/common': 1.5.2 '@codemirror/autocomplete@6.20.3': dependencies: '@codemirror/language': 6.12.4 - '@codemirror/state': 6.7.1 - '@codemirror/view': 6.43.9 + '@codemirror/state': 6.7.2 + '@codemirror/view': 6.43.11 '@lezer/common': 1.5.2 '@codemirror/commands@6.10.2': dependencies: '@codemirror/language': 6.12.4 - '@codemirror/state': 6.7.1 - '@codemirror/view': 6.43.9 + '@codemirror/state': 6.7.2 + '@codemirror/view': 6.43.11 '@lezer/common': 1.5.2 '@codemirror/commands@6.11.0': @@ -9530,8 +9679,8 @@ snapshots: '@codemirror/autocomplete': 6.20.3 '@codemirror/language': 6.12.4 '@codemirror/lint': 6.9.6 - '@codemirror/state': 6.7.1 - '@codemirror/view': 6.43.9 + '@codemirror/state': 6.7.2 + '@codemirror/view': 6.43.11 '@lezer/common': 1.5.2 '@lezer/javascript': 1.5.4 @@ -9681,8 +9830,8 @@ snapshots: '@codemirror/language@6.12.4': dependencies: - '@codemirror/state': 6.7.1 - '@codemirror/view': 6.43.9 + '@codemirror/state': 6.7.2 + '@codemirror/view': 6.43.11 '@lezer/common': 1.5.2 '@lezer/highlight': 1.2.3 '@lezer/lr': 1.4.10 @@ -9694,14 +9843,14 @@ snapshots: '@codemirror/lint@6.9.4': dependencies: - '@codemirror/state': 6.7.1 - '@codemirror/view': 6.43.9 + '@codemirror/state': 6.7.2 + '@codemirror/view': 6.43.11 crelt: 1.0.7 '@codemirror/lint@6.9.6': dependencies: - '@codemirror/state': 6.7.1 - '@codemirror/view': 6.43.9 + '@codemirror/state': 6.7.2 + '@codemirror/view': 6.43.11 crelt: 1.0.7 '@codemirror/merge@6.12.2': @@ -9714,14 +9863,10 @@ snapshots: '@codemirror/search@6.6.0': dependencies: - '@codemirror/state': 6.7.1 - '@codemirror/view': 6.43.9 + '@codemirror/state': 6.7.2 + '@codemirror/view': 6.43.11 crelt: 1.0.7 - '@codemirror/state@6.7.1': - dependencies: - '@marijn/find-cluster-break': 1.0.3 - '@codemirror/state@6.7.2': dependencies: '@marijn/find-cluster-break': 1.0.4 @@ -9733,13 +9878,6 @@ snapshots: style-mod: 4.1.3 w3c-keyname: 2.2.8 - '@codemirror/view@6.43.9': - dependencies: - '@codemirror/state': 6.7.1 - crelt: 1.0.7 - style-mod: 4.1.3 - w3c-keyname: 2.2.8 - '@connectrpc/connect-node@1.7.0(@bufbuild/protobuf@1.10.0)(@connectrpc/connect@1.7.0(@bufbuild/protobuf@1.10.0))': dependencies: '@bufbuild/protobuf': 1.10.0 @@ -10211,6 +10349,36 @@ snapshots: '@floating-ui/utils@0.2.12': {} + '@grpc/grpc-js@1.14.4': + dependencies: + '@grpc/proto-loader': 0.8.1 + '@js-sdsl/ordered-map': 4.4.2 + + '@grpc/proto-loader@0.8.1': + dependencies: + lodash.camelcase: 4.3.0 + long: 5.3.2 + protobufjs: 7.6.6 + yargs: 17.7.3 + + '@heif2jpeg/heif2jpeg-darwin-arm64@0.1.6': + optional: true + + '@heif2jpeg/heif2jpeg-darwin-x64@0.1.6': + optional: true + + '@heif2jpeg/heif2jpeg-linux-arm64-gnu@0.1.6': + optional: true + + '@heif2jpeg/heif2jpeg-linux-x64-gnu@0.1.6': + optional: true + + '@heif2jpeg/heif2jpeg-win32-arm64-msvc@0.1.6': + optional: true + + '@heif2jpeg/heif2jpeg-win32-x64-msvc@0.1.6': + optional: true + '@hono/node-server@2.1.0(hono@4.13.2)': dependencies: hono: 4.13.2 @@ -10365,6 +10533,8 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.6.0 + '@js-sdsl/ordered-map@4.4.2': {} + '@lexical/a11y@0.48.0(typescript@7.0.2)': dependencies: '@lexical/extension': 0.48.0(typescript@7.0.2) @@ -10690,8 +10860,6 @@ snapshots: '@lezer/highlight': 1.2.3 '@lezer/lr': 1.4.10 - '@marijn/find-cluster-break@1.0.3': {} - '@marijn/find-cluster-break@1.0.4': {} '@mdx-js/react@3.1.1(@types/react@19.2.18)(react@19.2.8)': @@ -11145,6 +11313,14 @@ snapshots: dependencies: '@noble/hashes': 1.8.0 + '@photon-ai/advanced-imessage@2.1.0(@grpc/grpc-js@1.14.4)(nice-grpc-common@2.0.4)(nice-grpc@2.1.17)': + dependencies: + '@bufbuild/protobuf': 2.15.0 + optionalDependencies: + '@grpc/grpc-js': 1.14.4 + nice-grpc: 2.1.17 + nice-grpc-common: 2.0.4 + '@pierre/diffs@1.3.6(@shikijs/themes@4.4.3)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: '@pierre/theme': 2.0.0 @@ -11180,6 +11356,26 @@ snapshots: '@preact/signals-core@1.14.4': {} + '@protobufjs/aspromise@1.1.2': {} + + '@protobufjs/base64@1.1.2': {} + + '@protobufjs/codegen@2.0.5': {} + + '@protobufjs/eventemitter@1.1.1': {} + + '@protobufjs/fetch@1.1.1': + dependencies: + '@protobufjs/aspromise': 1.1.2 + + '@protobufjs/float@1.0.2': {} + + '@protobufjs/path@1.1.2': {} + + '@protobufjs/pool@1.1.0': {} + + '@protobufjs/utf8@1.1.2': {} + '@radix-ui/colors@3.0.0': {} '@radix-ui/number@1.1.3': {} @@ -13075,6 +13271,8 @@ snapshots: '@xterm/xterm@6.0.0': {} + abort-controller-x@0.5.0: {} + accepts@2.0.0: dependencies: mime-types: 3.0.2 @@ -13406,6 +13604,12 @@ snapshots: classnames@2.5.1: {} + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + clsx@2.1.1: {} cm6-theme-basic-light@0.2.0(@codemirror/language@6.12.4)(@codemirror/state@6.7.2)(@codemirror/view@6.43.11)(@lezer/highlight@1.2.3): @@ -13434,8 +13638,8 @@ snapshots: '@codemirror/language': 6.12.4 '@codemirror/lint': 6.9.4 '@codemirror/search': 6.6.0 - '@codemirror/state': 6.7.1 - '@codemirror/view': 6.43.9 + '@codemirror/state': 6.7.2 + '@codemirror/view': 6.43.11 color-convert@2.0.1: dependencies: @@ -14370,6 +14574,15 @@ snapshots: dependencies: '@types/hast': 3.0.5 + heif2jpeg@0.1.6: + optionalDependencies: + '@heif2jpeg/heif2jpeg-darwin-arm64': 0.1.6 + '@heif2jpeg/heif2jpeg-darwin-x64': 0.1.6 + '@heif2jpeg/heif2jpeg-linux-arm64-gnu': 0.1.6 + '@heif2jpeg/heif2jpeg-linux-x64-gnu': 0.1.6 + '@heif2jpeg/heif2jpeg-win32-arm64-msvc': 0.1.6 + '@heif2jpeg/heif2jpeg-win32-x64-msvc': 0.1.6 + help-me@5.0.0: {} hono@4.13.2: {} @@ -14724,6 +14937,8 @@ snapshots: lodash-es@4.18.1: {} + lodash.camelcase@4.3.0: {} + lodash.clonedeep@4.5.0: {} lodash.includes@4.3.0: {} @@ -14744,6 +14959,8 @@ snapshots: lodash@4.18.1: {} + long@5.3.2: {} + longest-streak@3.1.0: {} loose-envify@1.4.0: @@ -15374,6 +15591,16 @@ snapshots: negotiator@1.0.0: {} + nice-grpc-common@2.0.4: + dependencies: + ts-error: 1.0.6 + + nice-grpc@2.1.17: + dependencies: + '@grpc/grpc-js': 1.14.4 + abort-controller-x: 0.5.0 + nice-grpc-common: 2.0.4 + node-domexception@1.0.0: {} node-fetch@3.3.2: @@ -15732,6 +15959,20 @@ snapshots: property-information@7.2.0: {} + protobufjs@7.6.6: + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.5 + '@protobufjs/eventemitter': 1.1.1 + '@protobufjs/fetch': 1.1.1 + '@protobufjs/float': 1.0.2 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.2 + '@types/node': 24.13.3 + long: 5.3.2 + proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 @@ -16024,6 +16265,8 @@ snapshots: remend@1.3.1: {} + require-directory@2.1.1: {} + require-from-string@2.0.2: {} requires-port@1.0.0: {} @@ -16545,6 +16788,8 @@ snapshots: ts-dedent@2.3.0: {} + ts-error@1.0.6: {} + ts-mixer@6.0.4: {} tsconfig-paths@4.2.0: @@ -16893,10 +17138,24 @@ snapshots: xtend@4.0.2: {} + y18n@5.0.8: {} + yallist@3.1.1: {} yallist@4.0.0: {} + yargs-parser@21.1.1: {} + + yargs@17.7.3: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + yjs@13.6.32: dependencies: lib0: 0.2.117 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index bec2de453b..026c899c97 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -36,3 +36,10 @@ overrides: rollup: ">=4.59.0" react: "^19.2.8" react-dom: "^19.2.8" + # CodeMirror validates extensions with instanceof, so exactly one copy of + # these two packages may resolve. Different CodeMirror packages pin + # different transitive minors, which resolves two copies and crashes the + # editor with "Unrecognized extension value in extension set". A shared + # range forces every consumer onto one resolution. + "@codemirror/state": "^6.7.2" + "@codemirror/view": "^6.43.11" diff --git a/scripts/check-docker-runner-cache.sh b/scripts/check-docker-runner-cache.sh index 0c8caf81d5..3456948793 100644 --- a/scripts/check-docker-runner-cache.sh +++ b/scripts/check-docker-runner-cache.sh @@ -1,10 +1,18 @@ #!/usr/bin/env bash -# Build the real Docker target twice in a disposable copy of tracked source. +# Build the real Docker target on two fresh builders using an exported cache. # Export only metadata, avoiding a multi-gigabyte test image in the daemon. set -euo pipefail repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" probe_dir="$(mktemp -d "${TMPDIR:-/tmp}/paperclip-runner-cache.XXXXXX")" -trap 'rm -rf "$probe_dir"' EXIT +baseline_builder="${probe_dir##*/}-baseline" +rebuild_builder="${probe_dir##*/}-rebuild" +cleanup() { + for builder in "$baseline_builder" "$rebuild_builder"; do + docker buildx rm "$builder" >/dev/null 2>&1 || true + done + rm -rf "$probe_dir" +} +trap cleanup EXIT mkdir "$probe_dir/context" cd "$repo_root" git ls-files -z | tar -cf - --null -T - | tar -xf - -C "$probe_dir/context" @@ -21,9 +29,16 @@ FROM scratch AS recipe-proof-export COPY --from=runner-plan /tmp/runner-recipe.json /recipe.json DOCKER build_proof() { - docker buildx build --file "$probe_dir/cache-probe.Dockerfile" --target cache-proof-export --output "type=local,dest=$probe_dir/$1" --progress plain . 2>&1 | tee "$probe_dir/$1.log" + local result="$1" builder="$2" + shift 2 + docker buildx build --builder "$builder" --file "$probe_dir/cache-probe.Dockerfile" --target cache-proof-export --output "type=local,dest=$probe_dir/$result" --progress plain "$@" . 2>&1 | tee "$probe_dir/$result.log" } -build_proof baseline +docker buildx create --name "$baseline_builder" --driver docker-container +build_proof baseline "$baseline_builder" --cache-to "type=local,dest=$probe_dir/cache,mode=max" +# Removing the first builder proves the second build cannot use daemon-local +# state, and releases its disk space before importing the exported cache. +docker buildx rm "$baseline_builder" +docker buildx create --name "$rebuild_builder" --driver docker-container python3 - <<'CHECK' from pathlib import Path p=Path('packages/paperclip-runner/runner/crates/runner-core/src/bin/paperclip-runnerd.rs') @@ -31,7 +46,7 @@ s=p.read_text(); needle='paperclip-runner/runnerd-build-metadata/v1' assert s.count(needle)==1 p.write_text(s.replace(needle,needle+'-cache-probe')) CHECK -build_proof source-change +build_proof source-change "$rebuild_builder" --cache-from "type=local,src=$probe_dir/cache" python3 - <<'CHECK' import os,json,re from pathlib import Path @@ -45,12 +60,12 @@ log=(root/'source-change.log').read_text() step=re.search(r'#(\d+) \[runner-deps[^\n]+ RUN cargo chef cook',log)[1] assert f'#{step} CACHED' in log assert 'Compiling paperclip-runner-core' in log -print('PASS: unchanged dependency recipe and cached cook layer; real binary changed.') +print('PASS: fresh builder imported compiled dependencies; real binary changed.') p=Path('packages/paperclip-runner/runner/Cargo.toml') s=p.read_text(); assert 'serde_json = "1.0"' in s p.write_text(s.replace('serde_json = "1.0"','serde_json = ">=1.0.0, <2.0.0"')) CHECK -docker buildx build --file "$probe_dir/cache-probe.Dockerfile" --target recipe-proof-export --output "type=local,dest=$probe_dir/manifest-change" --progress plain . +docker buildx build --builder "$rebuild_builder" --file "$probe_dir/cache-probe.Dockerfile" --target recipe-proof-export --output "type=local,dest=$probe_dir/manifest-change" --progress plain . python3 - <<'CHECK' from pathlib import Path import os diff --git a/scripts/ingest-app-definitions.mjs b/scripts/ingest-app-definitions.mjs index 164bf4f53f..fb34cc8d6d 100644 --- a/scripts/ingest-app-definitions.mjs +++ b/scripts/ingest-app-definitions.mjs @@ -65,6 +65,7 @@ const chatProviderName = (provider) => "microsoft-teams": "Microsoft Teams", slack: "Slack", telegram: "Telegram", + "imessage-photon": "iMessage Photon", })[provider]; const channelMethod = ( provider, @@ -364,6 +365,14 @@ const apps = [ }, ), ], + [ + "imessage-photon", "iMessage Photon", + "Message a Paperclip agent from Apple Messages using Photon Cloud. Pro supports DMs; dedicated lines also support groups.", + "communication", "photon.codes", ["https://photon.codes/*"], + channelMethod("imessage-photon", [field("projectSecret", "Project secret", "Photon project secret")], ["direct_message", "group_chat"], + "Connect a Photon Cloud project. Pro shared lines support DMs after sender enrollment in Photon and identity linking in Paperclip. Dedicated lines also support individually enabled groups.", + { register: "https://photon.codes/", docs: "https://photon.codes/docs/spectrum-ts/providers/imessage/connection-and-routing" }), + ], [ "telegram", "Telegram", @@ -1476,6 +1485,13 @@ const inferState = (slug, state) => { linkCount: state.links.length, }; }; +// Runtime credentials share the provider catalog, but never expose tool actions. +for (const [slug, name, subscription, envKey] of [["anthropic", "Claude", true, "ANTHROPIC_API_KEY"], ["openai", "OpenAI", true, "OPENAI_API_KEY"], ["openrouter", "OpenRouter", false, "OPENROUTER_API_KEY"], ["xai", "Grok", true, "XAI_API_KEY"]]) { + let app=apps.find(a=>a.slug===slug); + if(!app){app={schemaVersion:1,slug,name,description:`Connect ${name} accounts for your agents.`,categories:["ai"],branding:brandingFor(slug),urlPatterns:[{"openai":"https://api.openai.com/*","openrouter":"https://openrouter.ai/api/*","xai":"https://api.x.ai/*"}[slug]],methods:[]};apps.push(app);} + const methods=(subscription?["subscription","api_key"]:["api_key"]).map(authMethod=>({key:`ai-${authMethod}`,label:authMethod==="subscription"?`${name} subscription`:`${name} API key`,purpose:"ai",transport:"runtime_auth",auth:authMethod==="subscription"?"oauth":"api_key",ai:{provider:slug,method:authMethod},grantKinds:["user","organization"],ownershipModes:["customer"],whenToUse:"Authenticate an agent with this account.",guidanceMd:"Use your personal account or an explicitly shared company account.",riskTier:"S3",...(authMethod==="api_key"?{credentialFields:[field("apiKey","API key","Enter API key")],keyPlacement:{location:"env",name:envKey}}:{})})); + app.methods.unshift(...methods); +} const validateApp = (app) => { if ( app.schemaVersion !== 1 || diff --git a/scripts/preview-artifacts.test.mjs b/scripts/preview-artifacts.test.mjs index 15ca6978f2..ebdd9a6122 100644 --- a/scripts/preview-artifacts.test.mjs +++ b/scripts/preview-artifacts.test.mjs @@ -4,6 +4,7 @@ import { planArtifacts } from "./preview-artifacts.mjs"; import { readFileSync, mkdtempSync, writeFileSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import path from "node:path"; +import { fileURLToPath } from "node:url"; import { gzipSync } from "node:zlib"; import { execFileSync, spawnSync } from "node:child_process"; import { previewManifest, assertMetadata, validateRequest, versionFor, tarManifest, packageExists, imageExists, publishPreview, publishImage } from "./preview-artifacts.mjs"; @@ -217,8 +218,13 @@ test("cloud builds bake the managed runtime identity and verify it before public test("cloud cache imports are bounded, follow master ancestry, and retain the legacy fallback", () => { const workflow = readFileSync(new URL("../.github/workflows/docker-cloud.yml", import.meta.url), "utf8"); - const step = workflow.split(" - name: Select cloud cache ancestry")[1].split(" - name: Setup pnpm")[0]; - const script = step.split(" run: |\n")[1].split("\n").map((line) => line.replace(/^ {10}/, "")).join("\n"); + const selector = workflow.indexOf(" - name: Select cloud cache ancestry"); + assert.ok(selector > workflow.indexOf(" - name: Login to GitHub Container Registry")); + assert.ok(selector > workflow.indexOf(" - name: Set up Docker Buildx")); + assert.ok(selector < workflow.indexOf(" - name: Build and push (cloud)")); + assert.match(workflow, /run: node scripts\/select-cloud-cache.mjs/); + assert.match(workflow, /cache-from: \$\{\{ steps.cloud-cache.outputs.source \}\}/); + const script = fileURLToPath(new URL("./select-cloud-cache.mjs", import.meta.url)); const dir = mkdtempSync(path.join(tmpdir(), "cloud-cache-test-")); const output = path.join(dir, "output"); const env = { ...process.env, GIT_AUTHOR_NAME: "Test", GIT_AUTHOR_EMAIL: "test@example.test", GIT_COMMITTER_NAME: "Test", GIT_COMMITTER_EMAIL: "test@example.test" }; @@ -235,14 +241,25 @@ test("cloud cache imports are bounded, follow master ancestry, and retain the le git("checkout", "master"); git("merge", "--no-ff", "topic", "-m", "merge topic"); commits.unshift(git("rev-parse", "HEAD")); - const result = spawnSync("bash", ["-c", script], { cwd: dir, encoding: "utf8", env: { ...env, CACHE_IMAGE: "ghcr.io/paperclipai/paperclip", GITHUB_OUTPUT: output } }); + const available = `ghcr.io/paperclipai/paperclip:buildcache-cloud-${commits[2]}`; + const inspections = path.join(dir, "inspections"); + writeFileSync(path.join(dir, "docker"), `#!/usr/bin/env node +const fs = require("node:fs"); +fs.appendFileSync(process.env.CACHE_INSPECTIONS, process.argv.at(-1) + "\\n"); +if (process.argv.at(-1) !== process.env.AVAILABLE_CACHE) { + process.stderr.write("manifest unknown"); + process.exit(1); +} +`, { mode: 0o755 }); + const result = spawnSync(process.execPath, [script], { + cwd: dir, encoding: "utf8", env: { + ...env, PATH: `${dir}${path.delimiter}${env.PATH}`, CACHE_IMAGE: "ghcr.io/paperclipai/paperclip", + GITHUB_OUTPUT: output, AVAILABLE_CACHE: available, CACHE_INSPECTIONS: inspections, + }, + }); assert.equal(result.status, 0, result.stderr); - assert.deepEqual(readFileSync(output, "utf8").trim().split("\n"), [ - "sources< `type=registry,ref=ghcr.io/paperclipai/paperclip:buildcache-cloud-${commit}`), - "type=registry,ref=ghcr.io/paperclipai/paperclip:buildcache-cloud", - "CACHE_SOURCES", - ]); + assert.equal(readFileSync(output, "utf8"), `source=type=registry,ref=${available}\n`); + assert.deepEqual(readFileSync(inspections, "utf8").trim().split("\n"), commits.slice(0, 3).map((commit) => `ghcr.io/paperclipai/paperclip:buildcache-cloud-${commit}`)); } finally { rmSync(dir, { recursive: true, force: true }); } }); diff --git a/scripts/select-cloud-cache.mjs b/scripts/select-cloud-cache.mjs new file mode 100644 index 0000000000..74b05a533d --- /dev/null +++ b/scripts/select-cloud-cache.mjs @@ -0,0 +1,66 @@ +#!/usr/bin/env node +import { execFileSync } from "node:child_process"; +import { appendFileSync } from "node:fs"; +import { pathToFileURL } from "node:url"; + +export function cloudCacheCandidates(image, commits) { + if (!/^ghcr\.io\/[a-z0-9._-]+\/[a-z0-9._-]+$/.test(image ?? "")) { + throw new Error("Expected a GHCR owner/repository cache image."); + } + if (!Array.isArray(commits) || commits.length === 0 || commits.some((sha) => !/^[a-f0-9]{40}$/.test(sha))) { + throw new Error("Cloud cache ancestry requires full commit SHAs."); + } + return [ + ...[...new Set(commits)].slice(0, 10).map((sha) => `${image}:buildcache-cloud-${sha}`), + `${image}:buildcache-cloud`, + ]; +} + +export async function selectCloudCache(image, commits, { + exists = registryCacheExists, + log = console.log, +} = {}) { + for (const ref of cloudCacheCandidates(image, commits)) { + try { + if (!await exists(ref)) continue; + log(`Using cloud cache: ${ref}`); + return `type=registry,ref=${ref}`; + } catch { + // Cache availability must not turn an otherwise valid build into a + // failure. A later ancestor may still be available during a rollout. + log(`Could not inspect cloud cache ${ref}; trying the next ancestor.`); + } + } + log("No cloud cache is available; this build will populate one."); + return ""; +} + +function registryCacheExists(ref) { + try { + // Use the preceding Docker login, including for private registry caches. + // Inspect metadata only: no layer download and no image execution. + execFileSync("docker", ["buildx", "imagetools", "inspect", "--raw", ref], { + timeout: 10_000, + maxBuffer: 1024 * 1024, + stdio: ["ignore", "pipe", "pipe"], + }); + return true; + } catch (error) { + if (/manifest unknown|not found|NAME_UNKNOWN/i.test(String(error.stderr ?? ""))) return false; + throw error; + } +} + +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + try { + if (!process.env.GITHUB_OUTPUT) throw new Error("GITHUB_OUTPUT is required."); + const commits = execFileSync("git", ["rev-list", "--first-parent", "--max-count=10", "HEAD"], { + encoding: "utf8", + }).trim().split("\n"); + const source = await selectCloudCache(process.env.CACHE_IMAGE, commits, { exists: registryCacheExists }); + appendFileSync(process.env.GITHUB_OUTPUT, `source=${source}\n`); + } catch (error) { + console.error(error.message); + process.exitCode = 1; + } +} diff --git a/scripts/select-cloud-cache.test.mjs b/scripts/select-cloud-cache.test.mjs new file mode 100644 index 0000000000..d160fa4f04 --- /dev/null +++ b/scripts/select-cloud-cache.test.mjs @@ -0,0 +1,65 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { cloudCacheCandidates, selectCloudCache } from "./select-cloud-cache.mjs"; + +const image = "ghcr.io/paperclipai/paperclip"; +const commits = ["a".repeat(40), "b".repeat(40), "c".repeat(40)]; +const candidates = cloudCacheCandidates(image, commits); + +test("a same-SHA rerun imports only its existing cache", async () => { + const inspected = []; + const source = await selectCloudCache(image, commits, { + exists: async (ref) => { inspected.push(ref); return true; }, log() {}, + }); + assert.equal(source, `type=registry,ref=${candidates[0]}`); + assert.deepEqual(inspected, candidates.slice(0, 1)); +}); + +test("a new merge imports only its nearest available ancestor", async () => { + const inspected = []; + const source = await selectCloudCache(image, commits, { + exists: async (ref) => { inspected.push(ref); return ref === candidates[1]; }, log() {}, + }); + assert.equal(source, `type=registry,ref=${candidates[1]}`); + assert.deepEqual(inspected, candidates.slice(0, 2)); + assert.equal(source.includes("\n"), false); +}); + +test("a still-building parent falls back to an older completed cache", async () => { + assert.equal(await selectCloudCache(image, commits, { + exists: async (ref) => ref === candidates[2], log() {}, + }), `type=registry,ref=${candidates[2]}`); +}); + +test("the legacy cache is used only if no SHA cache exists", async () => { + const inspected = []; + assert.equal(await selectCloudCache(image, commits, { + exists: async (ref) => { inspected.push(ref); return ref === candidates.at(-1); }, log() {}, + }), `type=registry,ref=${candidates.at(-1)}`); + assert.deepEqual(inspected, candidates); +}); + +test("missing caches permit a cold build", async () => { + assert.equal(await selectCloudCache(image, commits, { exists: async () => false, log() {} }), ""); +}); + +test("a failed lookup can fall back without failing image publication", async () => { + const messages = []; + assert.equal(await selectCloudCache(image, commits, { + exists: async (ref) => { + if (ref === candidates[0]) throw new Error("registry temporarily unavailable"); + return true; + }, + log: (message) => messages.push(message), + }), `type=registry,ref=${candidates[1]}`); + assert.match(messages[0], /Could not inspect cloud cache/); +}); + +test("ancestry is bounded, deduplicated, and rejects output injection", () => { + const many = Array.from({ length: 20 }, (_, i) => i.toString(16).padStart(40, "0")); + assert.equal(cloudCacheCandidates(image, many).length, 11); + assert.deepEqual(cloudCacheCandidates(image, [commits[0], commits[0]]), [candidates[0], candidates.at(-1)]); + assert.throws(() => cloudCacheCandidates(`${image}\nsource=untrusted`, commits)); + assert.throws(() => cloudCacheCandidates(image, ["master"])); + assert.throws(() => cloudCacheCandidates(image, [])); +}); diff --git a/server/package.json b/server/package.json index 9ec984d294..d61cc8ee07 100644 --- a/server/package.json +++ b/server/package.json @@ -51,6 +51,7 @@ "@chat-adapter/teams": "4.39.0", "@chat-adapter/telegram": "4.39.0", "@discordjs/ws": "1.2.3", + "@grpc/grpc-js": "1.14.4", "@opentelemetry/api": "^1.9.0", "@paperclipai/adapter-claude-local": "workspace:*", "@paperclipai/adapter-codex-local": "workspace:*", @@ -68,6 +69,7 @@ "@paperclipai/plugin-sdk": "workspace:*", "@paperclipai/shared": "workspace:*", "@paperclipai/skills-catalog": "workspace:*", + "@photon-ai/advanced-imessage": "2.1.0", "@vercel/connect": "0.6.1", "acpx": "0.13.1", "ajv": "^8.20.0", @@ -81,8 +83,11 @@ "drizzle-orm": "^0.45.2", "embedded-postgres": "^18.1.0-beta.16", "express": "^5.1.0", + "heif2jpeg": "0.1.6", "jsdom": "^30.0.1", "multer": "^2.2.0", + "nice-grpc": "2.1.17", + "nice-grpc-common": "2.0.4", "open": "^11.0.1", "pino": "^10.0.0", "pino-http": "^11.0.0", diff --git a/server/src/__tests__/agent-live-run-routes.test.ts b/server/src/__tests__/agent-live-run-routes.test.ts index 55a74aa750..7520942040 100644 --- a/server/src/__tests__/agent-live-run-routes.test.ts +++ b/server/src/__tests__/agent-live-run-routes.test.ts @@ -425,7 +425,7 @@ describe("agent live run routes", () => { expect(res.body).not.toHaveProperty("resultJson"); expect(res.body).not.toHaveProperty("contextSnapshot"); expect(res.body).not.toHaveProperty("logRef"); - }, 10_000); + }); it("ignores a stale execution run from another issue and falls back to the assignee's matching run", async () => { mockHeartbeatService.getRunIssueSummary.mockResolvedValue({ @@ -832,6 +832,7 @@ describe("agent live run routes", () => { // Optional wake fields retain their existing shape; execution identity // always comes from the authenticated caller. expect(mockHeartbeatService.wakeup).toHaveBeenCalledWith(routeAgentId, { + manualUserWake: true, source: "on_demand", triggerDetail: "manual", reason: "issue_assigned", @@ -863,6 +864,7 @@ describe("agent live run routes", () => { expect(res.status, JSON.stringify(res.body)).toBe(202); expect(mockHeartbeatService.wakeup).toHaveBeenCalledWith(routeAgentId, { + manualUserWake: true, source: "on_demand", triggerDetail: "manual", requestedByActorType: "user", @@ -876,6 +878,18 @@ describe("agent live run routes", () => { }); }); + it.each(["wakeup", "heartbeat/invoke"])("lets an operator start an existing agent via %s without creating agents", async (endpoint) => { + mockAccessService.decide.mockImplementation(async ({ action }) => ({ + allowed: action === "agent:wake", explanation: "Missing permission: agents:create", + })); + const res = await requestApp(await createApp(undefined, { + type: "board", userId: "operator", source: "session", companyIds: ["company-1"], + }), url => request(url).post(`/api/agents/${routeAgentId}/${endpoint}`).send({})); + expect(res.status, JSON.stringify(res.body)).toBe(202); + expect(mockAccessService.decide).toHaveBeenCalledWith(expect.objectContaining({ action: "agent:wake" })); + expect(mockHeartbeatService.wakeup).toHaveBeenCalledWith(routeAgentId, expect.objectContaining({ manualUserWake: true })); + }); + describe("exact failed chat run retry", () => { const retryBody = { failedRunId: failedChatRunId, @@ -901,6 +915,10 @@ describe("agent live run routes", () => { companyId: "company-1", }); mockHeartbeatService.getRun.mockResolvedValue(selectedRun); + mockIssueService.getById.mockResolvedValue({ + id: failedChatIssueId, companyId: "company-1", assigneeAgentId: routeAgentId, + assigneeUserId: null, projectId: null, parentId: null, status: "blocked", + }); mockChatRunRetries.prepareFailedChatRunRetry.mockResolvedValue({ actionId: retryActionId, issueId: failedChatIssueId, @@ -913,6 +931,50 @@ describe("agent live run routes", () => { }); }); + it("retries a task for an operator without agent-creation permission", async () => { + const fixture = createFailedChatRetryDb(false); + mockHeartbeatService.getRun.mockResolvedValue({ ...selectedRun, contextSnapshot: { + issueId: failedChatIssueId, + } }); + mockAccessService.decide.mockImplementation(async ({ action }) => ({ + allowed: action === "issue:comment" || action === "agent:wake", explanation: "Missing permission: agents:create", + })); + const res = await requestApp(await createApp(fixture.db, { + type: "board", userId: "operator", source: "session", companyIds: ["company-1"], + }), url => request(url).post(`/api/agents/${routeAgentId}/wakeup`).send(retryBody)); + expect(res.status, JSON.stringify(res.body)).toBe(202); + expect(mockAccessService.decide).toHaveBeenCalledWith(expect.objectContaining({ + action: "issue:comment", resource: expect.objectContaining({ + type: "issue", companyId: "company-1", issueId: failedChatIssueId, + }), + })); + expect(mockHeartbeatService.wakeup).toHaveBeenCalledWith(routeAgentId, expect.objectContaining({ + requestedByActorType: "user", requestedByActorId: "operator", failedRunId: failedChatRunId, + payload: { issueId: failedChatIssueId }, + })); + }); + + it.each(["viewer", "missing", "other-company", "reassigned", "other-chat-owner"])( + "rejects a %s task retry without dispatching or requiring agent creation", async (fault) => { + const fixture = createFailedChatRetryDb(false); + mockHeartbeatService.getRun.mockResolvedValue({ ...selectedRun, contextSnapshot: { issueId: failedChatIssueId } }); + if (fault === "viewer") mockAccessService.decide.mockResolvedValue({ + allowed: false, explanation: "Viewer membership does not grant issue:comment.", + }); + else mockIssueService.getById.mockResolvedValue(fault === "missing" ? null : { + id: failedChatIssueId, companyId: fault === "other-company" ? "elsewhere" : "company-1", + assigneeAgentId: "other-agent", assigneeUserId: null, projectId: null, parentId: null, status: "blocked", + ...(fault === "other-chat-owner" ? { conversationAgentId: routeAgentId, conversationUserId: "someone-else" } : {}), + }); + const res = await requestApp(await createApp(fixture.db), url => + request(url).post(`/api/agents/${routeAgentId}/wakeup`).send(retryBody)); + expect(res.status).toBe(fault === "viewer" || fault === "other-chat-owner" ? 403 : fault === "reassigned" ? 409 : 404); + expect(mockAccessService.decide.mock.calls.every(([input]) => input.action !== "agents:create")).toBe(true); + expect(mockHeartbeatService.wakeup).not.toHaveBeenCalled(); + expect(mockChatRunRetries.prepareFailedChatRunRetry).not.toHaveBeenCalled(); + }, + ); + it.each([ ["failed", "deferred", null], ["timed_out", "running", "55555555-5555-4555-8555-555555555555"], @@ -1073,7 +1135,7 @@ describe("agent live run routes", () => { ); it.each(["agent", "company", "permission"])( - "denies %s authority before retry selection", + "denies %s authority before retry admission", async (denial) => { const fixture = createFailedChatRetryDb(); const actor = @@ -1615,7 +1677,7 @@ describe("agent live run routes", () => { id: "trace-1", status: "incomplete", deletedAt: null, - expiresAt: new Date(Date.now() + 60_000), + expiresAt: new Date("2099-01-01T00:00:00.000Z"), }, "trace_incomplete", ], diff --git a/server/src/__tests__/ai-connections.test.ts b/server/src/__tests__/ai-connections.test.ts new file mode 100644 index 0000000000..5bf2b0c060 --- /dev/null +++ b/server/src/__tests__/ai-connections.test.ts @@ -0,0 +1,527 @@ +import { connectionIntentService } from "../services/connection-intents.js"; +import { connectionIntentDeliveryService } from "../services/connection-intent-delivery.js"; +import { issueRecoveryActionService } from "../services/issue-recovery-actions.js"; +import * as localCredentials from "../services/local-ai-credentials.js"; +import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; +import { randomUUID } from "node:crypto"; +import { mkdtemp, rm, access, readFile, writeFile } from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { and, eq, sql } from "drizzle-orm"; +import { createDb, companies, agents, heartbeatRuns, companyMemberships, connectionGrants, connectionGrantDelegations, connectionGrantMembers, toolConnections, toolConnectionInstalls, aiConnectionDefaults, adapterAuthSessions, environments, issues, issueThreadInteractions, issueRecoveryActions, connectionIntentDeliveries, agentWakeupRequests } from "@paperclipai/db"; +import { startEmbeddedPostgresTestDatabase } from "@paperclipai/db/test-embedded-postgres"; +import { aiConnectionService } from "../services/ai-connections.js"; +import * as executionTarget from "@paperclipai/adapter-utils/execution-target"; +import { prepareManagedAiRuntime, assertManagedAiProjectAuth } from "../services/ai-connection-runtime.js"; +import { toolAccessService } from "../services/tool-access.js"; +import { secretService } from "../services/secrets.js"; +import { connectionPurposeTransportSchema, isAiConnectionCompatible } from "@paperclipai/shared"; +import express from "express"; +import request from "supertest"; +import { aiConnectionRoutes, canInstallSharedAiConnectionForNewAgent, responsibleUserForAiRequest } from "../routes/ai-connections.js"; +import { validateAiApiKey } from "../routes/ai-connections.js"; + +let database: Awaited>; +let db: ReturnType; +let home: string; +const companyId = randomUUID(); +const otherCompanyId = randomUUID(); +const agentId = randomUUID(); +let service: ReturnType; +const binding = { provider: "anthropic", method: "api_key", mode: "responsible_user" } as const; +const input = { companyId, agentId, adapterType: "claude_local", binding }; +const create = (userId: string, name: string, ownership: "personal" | "shared" = "personal") => service.save(companyId, userId, { provider: "anthropic", method: "api_key", ownership, name, apiKey: "fixture", agentIds: [], allAgents: true }, `fixture-${name}`); + +beforeAll(async () => { + home = await mkdtemp(path.join(os.tmpdir(), "paperclip-ai-tests-")); + vi.stubEnv("PAPERCLIP_HOME", home); + vi.stubEnv("PAPERCLIP_INSTANCE_ID", "ai-connection-fixture"); + database = await startEmbeddedPostgresTestDatabase("paperclip-ai-db-"); + db = createDb(database.connectionString); + service = aiConnectionService(db); + await db.insert(companies).values([{ id: companyId, name: "AI connection tests", issuePrefix: "AIT" }, { id: otherCompanyId, name: "Other", issuePrefix: "AIO" }]); + await db.insert(agents).values({ id: agentId, companyId, name: "Nova", adapterType: "claude_local" }); + await db.insert(companyMemberships).values(["alice", "bob"].map(principalId => ({ companyId, principalId, principalType: "user", status: "active", membershipRole: "member" }))); +}, 90000); +afterAll(async () => { await database?.cleanup(); vi.unstubAllEnvs(); if (home) await rm(home, { recursive: true, force: true }); }); + +describe("managed AI connections", () => { + it("checks the selected environment for project auth overrides without exposing their contents", async () => { + const execute = vi.spyOn(executionTarget, "runAdapterExecutionTargetProcess"); + const target = { kind: "remote", transport: "sandbox", remoteCwd: "/workspace/project" } as Parameters[2]; + try { + execute.mockResolvedValue({ exitCode: 42, stdout: "", stderr: "", signal: null, timedOut: false } as Awaited>); + await expect(assertManagedAiProjectAuth({}, "openai", target)).rejects.toThrow("project authentication settings"); + expect(execute.mock.calls[0][3]).toContain("/workspace/project"); + expect(execute.mock.calls[0][3]).toContain(".codex/config.toml"); + execute.mockResolvedValue({ exitCode: 0, stdout: "", stderr: "", signal: null, timedOut: false } as Awaited>); + await expect(assertManagedAiProjectAuth({}, "openai", target)).resolves.toBeUndefined(); + await expect(assertManagedAiProjectAuth({ args: ["--api-key=override"] }, "xai", target)).rejects.toThrow("overrides"); + } finally { execute.mockRestore(); } + }); + it("keeps personal defaults separate and does not replace the first default", async () => { + const first = await create("alice", "Alice first"); + await create("alice", "Alice second"); + const bob = await create("bob", "Bob first"); + const [a,b] = await Promise.all([service.select({ ...input, userId: "alice" }), service.select({ ...input, userId: "bob" })]); + expect(a.grant.id).toBe(first.grantId); expect(b.grant.id).toBe(bob.grantId); + expect(await service.credential(a)).toBe("fixture-Alice first"); + expect(await service.credential(b)).toBe("fixture-Bob first"); + expect(JSON.stringify(await service.list(companyId, "alice"))).not.toContain("fixture-"); + expect(await service.list(otherCompanyId, "alice")).toEqual([]); + }); + it("retains a revoked default without automatic fallback", async () => { + const selected = await service.select({ ...input, userId: "alice" }); + await db.update(connectionGrants).set({ status: "revoked" }).where(eq(connectionGrants.id, selected.grant.id)); + await create("alice", "Alice third"); + expect(await toolAccessService(db).getConnection(selected.connection.id, companyId)).toMatchObject({ healthStatus: "missing_secret", requiresReauthorization: true }); + expect((await toolAccessService(db).listConnections(companyId)).find(connection => connection.id === selected.connection.id)?.healthStatus).toBe("missing_secret"); + await expect(service.select({ ...input, userId: "alice" })).rejects.toThrow("Reconnect"); + const second = (await service.list(companyId, "alice")).find(a => a.name === "Alice second")!; + await service.setDefault(companyId, "alice", second.grantId); + expect((await service.select({ ...input, userId: "alice" })).grant.id).toBe(second.grantId); + await expect(service.setDefault(companyId, "bob", second.grantId)).rejects.toThrow("owner"); + }); + it("uses human access for every selection; an agent delegation cannot override Just me", async () => { + const personal = await service.select({ ...input, userId: "alice" }); + const delegated = { ...binding, mode: "delegated" as const, connectionId: personal.connection.id, grantId: personal.grant.id }; + await expect(service.select({ ...input, userId: "bob", binding: delegated })).rejects.toThrow("not shared"); + // Existing delegation records no longer confer an independent AI permission. + await db.insert(connectionGrantDelegations).values({ companyId, grantId: personal.grant.id, agentId, createdByUserId: "alice" }); + await expect(service.select({ ...input, userId: "bob", binding: delegated })).rejects.toThrow("not shared"); + expect((await service.select({ ...input, userId: "alice", binding: delegated })).grant.id).toBe(personal.grant.id); + expect((await service.list(companyId, "bob", agentId)).some(account => account.id === personal.connection.id)).toBe(false); + await expect(toolAccessService(db).createConnectionGrantDelegation(personal.connection.id, personal.grant.id, agentId, "alice")).rejects.toThrow("human access settings"); + }); + it("applies the existing human audience editor to AI listing and execution without a second authorization", async () => { + const shared = await create("alice", "Engineering", "shared"); + const sharedBinding = { ...binding, mode: "shared" as const, ...shared }; + const tools = toolAccessService(db); + await tools.replaceConnectionGrantMembers(shared.connectionId, shared.grantId, ["alice"], { userId: "alice" }); + await expect(service.select({ ...input, userId: "bob", binding: sharedBinding })).rejects.toThrow("not shared"); + expect((await service.list(companyId, "bob", agentId)).some(account => account.id === shared.connectionId)).toBe(false); + await tools.replaceConnectionGrantMembers(shared.connectionId, shared.grantId, ["bob"], { userId: "alice" }); + expect((await service.select({ ...input, userId: "bob", binding: sharedBinding })).grant.id).toBe(shared.grantId); + expect((await service.list(companyId, "bob", agentId)).some(account => account.id === shared.connectionId)).toBe(true); + await expect(service.select({ ...input, userId: "alice", binding: sharedBinding })).rejects.toThrow("not shared"); + await tools.replaceConnectionGrantMembers(shared.connectionId, shared.grantId, [], { userId: "alice" }); + for (const userId of ["alice", "bob"]) { + expect((await service.select({ ...input, userId, binding: sharedBinding })).grant.id).toBe(shared.grantId); + } + await expect(service.select({ ...input, userId: null, binding: sharedBinding })).rejects.toThrow("not shared"); + // Human permission still cannot bypass the separate agent-access setting. + await db.delete(toolConnectionInstalls).where(eq(toolConnectionInstalls.connectionId, shared.connectionId)); + await expect(service.select({ ...input, userId: "bob", binding: sharedBinding })).rejects.toThrow("not permitted for this agent"); + expect(sharedBinding).toEqual({ ...binding, mode: "shared", ...shared }); + }); + it("isolates concurrent homes and overrides ambient credentials without changing the model", async () => { + vi.stubEnv("ANTHROPIC_API_KEY", "ambient-never-use"); + const config = { model: "unchanged-model", env: { ANTHROPIC_API_KEY: "project-never-use" } }; + const [a,b] = await Promise.all(["alice", "bob"].map(responsibleUserId => prepareManagedAiRuntime(db, { ...input, responsibleUserId, config }))); + const ae = a.config.env as Record, be = b.config.env as Record; + expect(ae.ANTHROPIC_API_KEY).toBe("fixture-Alice second"); expect(be.ANTHROPIC_API_KEY).toBe("fixture-Bob first"); + expect(ae.HOME).not.toBe(be.HOME); expect(a.identity).not.toBe(b.identity); + expect(a.config.model).toBe("unchanged-model"); expect(config.env.ANTHROPIC_API_KEY).toBe("project-never-use"); + await Promise.all([a.cleanup(), b.cleanup()]); await expect(access(ae.HOME)).rejects.toThrow(); + }); + it("blocks missing identity, incompatible providers, and cross-company explicit selections", async () => { + await expect(service.select({ ...input, userId: null })).rejects.toThrow("responsible user"); + await expect(service.select({ ...input, userId: "alice", adapterType: "codex_local" })).rejects.toThrow("compatible"); + const account = await service.select({ ...input, userId: "alice" }); + await expect(service.select({ ...input, companyId: otherCompanyId, userId: "alice", binding: { ...binding, mode: "shared", connectionId: account.connection.id, grantId: account.grant.id } })).rejects.toThrow(); + }); + it("resolves shared encrypted credentials through the existing secret binding system", async () => { + const created = await create("alice", "Shared credential proof", "shared"); + const selected = await service.select({ ...input, userId: "bob", binding: { ...binding, mode: "shared", ...created } }); + expect(await service.credential(selected)).toBe("fixture-Shared credential proof"); + }); + it("saves successful login completion once and rejects abandoned attempts", async () => { + const [environment] = await db.insert(environments).values({ name: "AI login test", driver: "sandbox" }).returning(); + const intent = { provider: "anthropic", method: "subscription", ownership: "personal", name: "Claude subscription", agentIds: [], allAgents: true } as const; + const sessionId = randomUUID(); + await db.insert(adapterAuthSessions).values({ companyId, environmentId: environment.id, adapterType: "claude_local", startedByUserId: "alice", publicSessionId: sessionId, status: "submitting", aiConnection: { ...intent, agentIds: [] }, expiresAt: new Date(Date.now() + 60000) }); + const first = await service.save(companyId, "alice", { ...intent, agentIds: [] }, "fixture-subscription", sessionId); + expect(await service.save(companyId, "alice", { ...intent, agentIds: [] }, "fixture-subscription", sessionId)).toEqual(first); + const cancelled = randomUUID(); + await db.insert(adapterAuthSessions).values({ companyId, environmentId: environment.id, adapterType: "claude_local", startedByUserId: "alice", publicSessionId: cancelled, status: "cancelled", expiresAt: new Date(Date.now() + 60000) }); + await expect(service.save(companyId, "alice", { ...intent, agentIds: [] }, "fixture-never-save", cancelled)).rejects.toThrow("no longer active"); + }); + it("preserves connection identity and defaults through reconnect; revocation wins over older attempts", async () => { + const current = await service.select({ ...input, userId: "bob" }); + const reconnect = { ...binding, ownership: "personal" as const, name: current.connection.name, apiKey: "fixture", agentIds: [], allAgents: true, connectionId: current.connection.id }; + const result = await service.save(companyId, "bob", reconnect, "fixture-reconnected"); + expect(result.grantId).toBe(current.grant.id); + expect(await service.credential(await service.select({ ...input, userId: "bob" }))).toBe("fixture-reconnected"); + const beforeRevocation = new Date(Date.now() - 1000); + await db.update(connectionGrants).set({ status: "revoked", updatedAt: new Date() }).where(eq(connectionGrants.id, current.grant.id)); + await expect(service.save(companyId, "bob", reconnect, "fixture-stale", undefined, beforeRevocation)).rejects.toThrow("changed"); + await expect(service.select({ ...input, userId: "bob" })).rejects.toThrow("Reconnect"); + }); + it("rejects invalid purpose/transport combinations in the database", async () => { + const selected = await service.select({ ...input, userId: "alice" }); + await expect(db.update(toolConnections).set({ transport: "mcp_remote" }).where(eq(toolConnections.id, selected.connection.id))).rejects.toThrow(); + await expect(db.update(toolConnections).set({ connectionPurpose: "tool" }).where(eq(toolConnections.id, selected.connection.id))).rejects.toThrow(); + }); + it("indexes only known user credentials, retains references, and is repeatable without adopting agents", async () => { + const selected = await service.select({ ...input, userId: "alice" }); + const [emailConnection] = await db.insert(toolConnections).values({ + companyId, + applicationId: selected.connection.applicationId, + name: "Existing AgentMail inbox", + uid: `agentmail-migration-${randomUUID()}`, + connectionPurpose: "channel", + transport: "rest_api", + authKind: "api_key", + config: { provider: "agentmail" }, + }).returning(); + const vault = secretService(db); + const definition = await vault.createUserSecretDefinition(companyId, { key: "legacy_claude", name: "Existing owned Claude key", provider: "local_encrypted" }, { userId: "alice" }); + const secret = await vault.createCurrentUserSecretValue(companyId, "alice", { definitionId: definition.id, value: "fixture-legacy" }, { userId: "alice" }); + await vault.syncUserSecretDeclarationsForTarget(companyId, { targetType: "agent", targetId: agentId }, [{ definitionKey: definition.key, configPath: "env.ANTHROPIC_API_KEY", envKey: "ANTHROPIC_API_KEY", required: true }]); + const migration = await readFile(new URL("../../../packages/db/src/migrations/0276_hard_mandroid.sql", import.meta.url), "utf8"); + const adoption = migration.slice(migration.indexOf("DO $$", migration.indexOf("-- Only declared"))); + await db.execute(sql.raw(adoption)); + const before = await service.list(companyId, "alice"); + for (const statement of migration.split("--> statement-breakpoint").filter(value => value.trim())) await db.execute(sql.raw(statement)); + expect(await service.list(companyId, "alice")).toEqual(before); + const [preservedEmail] = await db.select().from(toolConnections).where(eq(toolConnections.id, emailConnection.id)); + expect(preservedEmail).toEqual(emailConnection); + const indexed = before.find(account => account.name === secret.name)!; + expect(indexed.id).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/); + const [grant] = await db.select().from(connectionGrants).where(eq(connectionGrants.id, indexed.grantId)); + expect(grant.credentialSecretRefs[0].secretId).toBe(secret.id); + const [agent] = await db.select().from(agents).where(eq(agents.id, agentId)); + expect(agent.runtimeConfig.aiConnection).toBeUndefined(); + }); + it("serializes subscription refresh and releases the lease after execution", async () => { + const subscription = { ...input, binding: { ...binding, method: "subscription" as const }, responsibleUserId: "alice", config: { model: "same-model" } }; + const first = await prepareManagedAiRuntime(db, subscription); + await expect(prepareManagedAiRuntime(db, subscription)).rejects.toThrow("in use"); + await first.cleanup(); + const next = await prepareManagedAiRuntime(db, subscription); + expect(next.identity).toBe(first.identity); + await next.cleanup(); + }); + it("persists refreshed credentials only to their original grant and fences reconnects", async () => { + const auth = (marker: string, hour: number) => JSON.stringify({ tokens: { account_id: "fixture-account", id_token: `id-${marker}`, access_token: `access-${marker}`, refresh_token: `refresh-${marker}` }, last_refresh: `2026-09-10T${hour}:00:00Z` }); + const intent = { provider: "openai" as const, method: "subscription" as const, name: "Refresh test", ownership: "personal" as const, agentIds: [], allAgents: true, loginSessionId: "fixture" }; + const saved = await service.save(companyId, "alice", intent, auth("first", 10)); + const runInput = { ...input, adapterType: "codex_local", responsibleUserId: "alice", binding: { provider: "openai", method: "subscription", mode: "responsible_user" } as const, config: { model: "same-model" } }; + const first = await prepareManagedAiRuntime(db, runInput); + await writeFile(path.join(String(first.config.env.CODEX_HOME), "auth.json"), auth("refreshed", 11)); + await first.cleanup(); + const selected = await service.select({ ...runInput, userId: "alice" }); + expect(await service.credential(selected)).toBe(auth("refreshed", 11)); + const second = await prepareManagedAiRuntime(db, runInput); + expect(second.identity).not.toBe(first.identity); + await service.save(companyId, "alice", { ...intent, connectionId: saved.connectionId }, auth("reconnect", 12)); + await writeFile(path.join(String(second.config.env.CODEX_HOME), "auth.json"), auth("stale-process", 13)); + await second.cleanup(); + expect(await service.credential(await service.select({ ...runInput, userId: "alice" }))).toBe(auth("reconnect", 12)); + }); + it("enforces the shared transport discriminator and existing harness compatibility", () => { + expect(connectionPurposeTransportSchema.safeParse({ connectionPurpose: "ai", transport: "mcp_remote" }).success).toBe(false); + expect(connectionPurposeTransportSchema.safeParse({ connectionPurpose: "tool", transport: "runtime_auth" }).success).toBe(false); + expect(connectionPurposeTransportSchema.safeParse({ connectionPurpose: "channel", transport: "rest_api", config: { provider: "agentmail" } }).success).toBe(true); + expect(connectionPurposeTransportSchema.safeParse({ connectionPurpose: "channel", transport: "rest_api", config: { provider: "slack" } }).success).toBe(false); + expect(connectionPurposeTransportSchema.safeParse({ connectionPurpose: "channel", transport: "runtime_auth", config: { provider: "agentmail" } }).success).toBe(false); + expect(isAiConnectionCompatible(binding, "paperclip_runner", "same-model", "acpx", "claude")).toBe(true); + expect(isAiConnectionCompatible(binding, "paperclip_runner", "same-model", "acpx", "codex")).toBe(false); + expect(isAiConnectionCompatible({ provider: "openrouter", method: "api_key" }, "opencode_local", "anthropic/model")).toBe(false); + }); + it("does not let a forged delegation bypass human access or accept an expired subscription attempt", async () => { + const selected = await service.select({ ...input, userId: "alice" }); + const otherAgent = randomUUID(); + await db.insert(agents).values({ id: otherAgent, companyId, name: "Other" }); + await db.insert(connectionGrantDelegations).values({ companyId, grantId: selected.grant.id, agentId: otherAgent, createdByUserId: "bob" }); + await expect(service.select({ ...input, agentId: otherAgent, userId: "bob", binding: { ...binding, mode: "delegated", connectionId: selected.connection.id, grantId: selected.grant.id } })).rejects.toThrow("not shared"); + const [environment] = await db.select().from(environments).limit(1); + const sessionId = randomUUID(); + const intent = { provider: "anthropic", method: "subscription", ownership: "personal", name: "Expired", agentIds: [], allAgents: true } as const; + await db.insert(adapterAuthSessions).values({ companyId, environmentId: environment.id, adapterType: "claude_local", startedByUserId: "alice", publicSessionId: sessionId, status: "submitting", aiConnection: { ...intent, agentIds: [] }, expiresAt: new Date(Date.now() - 1000) }); + await expect(service.save(companyId, "alice", { ...intent, agentIds: [] }, "fixture-never-save", sessionId)).rejects.toThrow("no longer active"); + expect((await service.list(companyId, "alice")).some(account => account.name === "Expired")).toBe(false); + }); + it("authorizes account creation, reconnect and defaults at the HTTP boundary before provider calls", async () => { + const app = express(); + app.use(express.json()); + app.use((req, _res, next) => { + const userId = String(req.headers["x-test-user"] ?? "alice"); + const role = req.headers["x-test-role"] === "viewer" ? "viewer" : "member"; + req.actor = { type: "board", source: "session", userId, companyIds: [companyId], memberships: [{ companyId, membershipRole: role, status: "active" }] }; + next(); + }); + app.use("/api", aiConnectionRoutes(db)); + app.use((error: { status?: number; message: string }, _req: express.Request, res: express.Response, _next: express.NextFunction) => { res.status(error.status ?? 500).json({ error: error.message }); }); + const personal = await service.select({ ...input, userId: "alice" }); + const base = `/api/companies/${companyId}/ai-connections`; + expect((await request(app).get(`/api/companies/${otherCompanyId}/ai-connections`)).status).toBe(403); + expect((await request(app).put(`${base}/default`).set("x-test-user", "bob").send({ grantId: personal.grant.id })).status).toBe(403); + expect((await request(app).put(`${base}/default`).set("x-test-role", "viewer").send({ grantId: personal.grant.id })).status).toBe(403); + const payload = { provider: "anthropic", method: "api_key", name: "Fixture", ownership: "personal", apiKey: "fixture", allAgents: false, agentIds: [] }; + const network = vi.spyOn(globalThis, "fetch").mockRejectedValue(new Error("must not reach provider")); + try { + expect((await request(app).post(base).set("x-test-role", "viewer").send(payload)).status).toBe(403); + expect((await request(app).post(base).send({ ...payload, ownership: "shared" })).status).toBe(403); + expect((await request(app).post(base).set("x-test-user", "bob").send({ ...payload, connectionId: personal.connection.id })).status).toBe(403); + expect(network).not.toHaveBeenCalled(); + } finally { network.mockRestore(); } + }); + it("imports only for the local operator and preserves identity and permissions on reconnect", async () => { + const reader = vi.spyOn(localCredentials, "readVerifiedLocalAiCredential").mockResolvedValue("fixture-local-token"); + const app = express(); + app.use(express.json()); + app.use((req, _res, next) => { + req.actor = { type: "board", source: req.headers["x-local"] === "yes" ? "local_implicit" : "session", userId: String(req.headers["x-test-user"] ?? "alice"), companyIds: [companyId], memberships: [{ companyId, status: "active", membershipRole: "member" }] }; + next(); + }); + app.use("/api", aiConnectionRoutes(db)); + app.use((error: { status?: number; message: string }, _req: express.Request, res: express.Response, _next: express.NextFunction) => { res.status(error.status ?? 500).json({ error: error.message }); }); + const url = `/api/companies/${companyId}/ai-connections/local`; + const payload = { provider: "anthropic", method: "subscription", name: "Local account test", ownership: "personal", agentIds: [agentId], allAgents: false }; + try { + expect((await request(app).post(url).send(payload)).status).toBe(403); + expect(reader).not.toHaveBeenCalled(); + expect((await request(app).post(`${url}/check`).send(payload)).status).toBe(403); + expect(reader).not.toHaveBeenCalled(); + const checked = await request(app).post(`${url}/check`).set("x-local", "yes").send(payload); + expect(checked.status).toBe(200); + expect(checked.body).toEqual({ status: "ready" }); + expect((await service.list(companyId, "alice")).some(c => c.name === payload.name)).toBe(false); + const connected = await request(app).post(url).set("x-local", "yes").send(payload); + expect(connected.status).toBe(201); + expect(JSON.stringify(connected.body)).not.toContain("fixture-local-token"); + const before = await db.select().from(toolConnectionInstalls).where(eq(toolConnectionInstalls.connectionId, connected.body.connectionId)); + expect(before.map(i => [i.targetType, i.targetId])).toEqual([["agent", agentId]]); + const reconnected = await request(app).post(url).set("x-local", "yes").send({ ...payload, connectionId: connected.body.connectionId, allAgents: true }); + expect(reconnected.status).toBe(201); + expect(reconnected.body).toEqual(connected.body); + const after = await db.select().from(toolConnectionInstalls).where(eq(toolConnectionInstalls.connectionId, connected.body.connectionId)); + expect(after).toEqual(before); + reader.mockRejectedValueOnce(Object.assign(new Error("Sign in locally and retry"), { status: 422 })); + const failed = await request(app).post(url).set("x-local", "yes").send({ ...payload, name: "Unsuccessful local login" }); + expect(failed.status).toBe(422); + expect((await service.list(companyId, "alice")).some(c => c.name === "Unsuccessful local login")).toBe(false); + const codex = { ...payload, provider: "openai", name: "Isolated terminal login" }; + const attempts = `${url}/attempts`; + expect((await request(app).post(attempts).send(codex)).status).toBe(403); // This member cannot authorize agentId. + expect((await request(app).post(url).set("x-local", "yes").send(codex)).status).toBe(422); + const prepared = await request(app).post(attempts).set("x-local", "yes").send(codex); + expect(prepared.status).toBe(201); + expect(prepared.body.command).toMatch(/^\(export CODEX_HOME=.* && mkdir -p .* && codex -c .* login --device-auth\)$/); + expect((await request(app).post(attempts).set("x-local", "yes").send(codex)).body).toEqual(prepared.body); + expect((await request(app).delete(`${attempts}/${prepared.body.sessionId}`).set("x-test-user", "bob").send()).status).toBe(404); + expect((await request(app).delete(`${attempts}/${prepared.body.sessionId}`).set("x-local", "yes").send()).status).toBe(200); + expect((await request(app).post(url).set("x-local", "yes").send({ ...codex, localSessionId: prepared.body.sessionId })).status).toBe(422); + } finally { reader.mockRestore(); } + }); + it.each(["anthropic", "openai"] as const)("blocks server-host %s login on a public deployment without a trusted host", async provider => { + const reader = vi.spyOn(localCredentials, "readVerifiedLocalAiCredential"); + const app = express(); + app.use(express.json()); + app.use((req, _res, next) => { + req.actor = { type: "board", source: "session", userId: "alice", companyIds: [companyId], memberships: [{ companyId, status: "active", membershipRole: "member" }] }; + next(); + }); + app.use("/api", aiConnectionRoutes(db, { deploymentMode: "authenticated", deploymentExposure: "public", trustedLocalStdioRuntimeHost: "" })); + app.use((error: { status?: number; message: string }, _req: express.Request, res: express.Response, _next: express.NextFunction) => { res.status(error.status ?? 500).json({ error: error.message }); }); + const base = `/api/companies/${companyId}/ai-connections/local`; + const intent = { provider, method: "subscription", ownership: "personal", name: "Hosted account", allAgents: false, agentIds: [] }; + try { + for (const endpoint of [base, `${base}/attempts`, `${base}/check`]) { + const result = await request(app).post(endpoint).send(intent); + expect(result.status).toBe(422); + expect(result.body.error).toContain("unavailable on this hosted instance"); + } + expect(reader).not.toHaveBeenCalled(); + } finally { reader.mockRestore(); } + }); + it.each(["anthropic", "openai"] as const)("lets authenticated users connect only their own isolated %s login", async provider => { + const owner = `self-hosted-${provider}`; + await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" }); + const reader = vi.spyOn(localCredentials, "readVerifiedLocalAiCredential").mockResolvedValue("isolated-fixture-token"); + const app = express(); + app.use(express.json()); + app.use((req, _res, next) => { + req.actor = { type: "board", source: "session", userId: String(req.headers["x-test-user"] ?? owner), companyIds: [companyId], memberships: [{ companyId, status: "active", membershipRole: req.headers["x-viewer"] ? "viewer" : "member" }] }; + next(); + }); + app.use("/api", aiConnectionRoutes(db)); + app.use((error: { status?: number; message: string }, _req: express.Request, res: express.Response, _next: express.NextFunction) => { res.status(error.status ?? 500).json({ error: error.message }); }); + const base = `/api/companies/${companyId}/ai-connections/local`; + const intent = { provider, method: "subscription", ownership: "personal", name: `Self-hosted ${provider}`, allAgents: false, agentIds: [] }; + try { + expect((await request(app).post(`${base}/attempts`).set("x-viewer", "yes").send(intent)).status).toBe(403); + const started = await request(app).post(`${base}/attempts`).send(intent); + expect(started.status).toBe(201); + expect(started.headers["cache-control"]).toBe("no-store"); + expect(started.body.command).toContain(provider === "anthropic" ? "CLAUDE_CONFIG_DIR=" : "login --device-auth"); + expect((await request(app).post(`${base}/attempts`).send(intent)).body).toEqual(started.body); + const input = { ...intent, localSessionId: started.body.sessionId }; + for (const endpoint of [base, `${base}/check`]) { + expect((await request(app).post(endpoint).set("x-test-user", "bob").send(input)).status).toBe(404); + expect((await request(app).post(endpoint.replace(companyId, otherCompanyId)).send(input)).status).toBe(403); + } + expect(reader).not.toHaveBeenCalled(); + const checked = await request(app).post(`${base}/check`).send(input); + expect(checked.body).toEqual({ status: "ready" }); + expect(reader).toHaveBeenLastCalledWith(provider, path.join(home, "instances/ai-connection-fixture/ai-local-logins", started.body.sessionId)); + const saved = await request(app).post(base).send(input); + expect(saved.status).toBe(201); + expect((await request(app).post(base).send(input)).body).toEqual(saved.body); + expect(JSON.stringify(saved.body)).not.toContain("isolated-fixture-token"); + expect((await service.list(companyId, owner)).filter(c => c.name === intent.name)).toHaveLength(1); + } finally { reader.mockRestore(); } + }); + it("rejects invalid credentials without exposing the provider response", async () => { + const request = vi.fn().mockResolvedValue(new Response("secret-provider-body", { status: 401 })); + await expect(validateAiApiKey("anthropic", "fixture", request)).rejects.toThrow("rejected"); + expect(request.mock.calls[0][1].redirect).toBe("error"); + }); + it("uses the authenticated responsible user for agent-originated configuration and tests", async () => { + const req = { actor: { type: "agent", agentId, onBehalfOfUserId: "alice" } } as express.Request; + const selected = await service.select({ ...input, userId: responsibleUserForAiRequest(req) }); + expect(selected.grant.subjectUserId).toBe("alice"); + req.actor.onBehalfOfUserId = undefined; + expect(responsibleUserForAiRequest(req)).toBeNull(); + await expect(service.select({ ...input, userId: responsibleUserForAiRequest(req) })).rejects.toThrow(); + }); + + it("protects active-run attribution with the connection human audience", async () => { + const account = await create("alice", "Private run attribution"); + const runId = randomUUID(); + await db.insert(heartbeatRuns).values({ id: runId, companyId, agentId, status: "running", contextSnapshot: { aiConnection: { connectionId: account.connectionId, grantId: account.grantId } } }); + const app = express(); + app.use((req, _res, next) => { + req.actor = { type: "board", source: "session", userId: String(req.headers["x-test-user"] ?? "alice"), companyIds: [companyId] }; + next(); + }); + app.use("/api", aiConnectionRoutes(db)); + app.use((error: { status?: number; message: string }, _req: express.Request, res: express.Response, _next: express.NextFunction) => { res.status(error.status ?? 500).json({ error: error.message }); }); + const url = `/api/companies/${companyId}/ai-connections/${account.connectionId}/active-runs`; + const own = await request(app).get(url); + expect(own.status).toBe(200); + expect(own.headers["cache-control"]).toBe("no-store"); + expect(own.body).toEqual([expect.objectContaining({ id: runId, agentId })]); + expect((await request(app).get(url).set("x-test-user", "bob")).status).toBe(404); + await db.update(connectionGrants).set({ kind: "organization", subjectUserId: null }).where(eq(connectionGrants.id, account.grantId)); + await db.insert(connectionGrantMembers).values({ companyId, grantId: account.grantId, subjectType: "user", subjectId: "alice" }); + expect((await request(app).get(url).set("x-test-user", "bob")).status).toBe(404); + await db.insert(connectionGrantMembers).values({ companyId, grantId: account.grantId, subjectType: "user", subjectId: "bob" }); + expect((await request(app).get(url).set("x-test-user", "bob")).body).toEqual(own.body); + expect((await request(app).get(url.replace(companyId, otherCompanyId))).status).toBe(403); + }); + it("permits new-agent shared installation only for a connection configurator, without bypassing audience", async () => { + const account = await service.save(companyId, "alice", { provider: "anthropic", method: "api_key", ownership: "shared", name: "Restricted shared", apiKey: "fixture", agentIds: [], allAgents: false }, "fixture-restricted"); + const selected = { provider: "anthropic", method: "api_key", mode: "shared", ...account } as const; + const futureAgentId = randomUUID(); + const req = (userId: string, role = "member") => ({ actor: { type: "board", source: "session", userId, companyIds: [companyId], memberships: [{ companyId, membershipRole: role, status: "active" }] } }) as express.Request; + expect(await canInstallSharedAiConnectionForNewAgent(db, req("alice"), companyId, selected)).toBe(true); + expect(await canInstallSharedAiConnectionForNewAgent(db, req("bob"), companyId, selected)).toBe(false); + expect(await canInstallSharedAiConnectionForNewAgent(db, req("alice", "viewer"), companyId, selected)).toBe(false); + expect(await canInstallSharedAiConnectionForNewAgent(db, { actor: { type: "agent", onBehalfOfUserId: "alice" } } as express.Request, companyId, selected)).toBe(false); + const selectionInput = { ...input, agentId: futureAgentId, userId: "alice", binding: selected }; + await expect(service.select(selectionInput)).rejects.toThrow("not permitted for this agent"); + const run = await prepareManagedAiRuntime(db, { companyId, agentId: futureAgentId, responsibleUserId: "alice", adapterType: "claude_local", binding: selected, config: { cwd: home, model: "same-model" }, allowUninstalledShared: true }); + expect(run.config.model).toBe("same-model"); + await run.cleanup(); + await db.insert(connectionGrantMembers).values({ companyId, grantId: account.grantId, subjectType: "user", subjectId: "bob" }); + await expect(service.select({ ...selectionInput, allowUninstalledShared: true })).rejects.toThrow("not shared with the responsible user"); + await db.delete(connectionGrantMembers).where(eq(connectionGrantMembers.grantId, account.grantId)); + await db.insert(agents).values({ id: futureAgentId, companyId, name: "New shared agent", adapterType: "claude_local" }); + await db.insert(toolConnectionInstalls).values({ companyId, connectionId: account.connectionId, targetType: "agent", targetId: futureAgentId, createdByUserId: "alice" }); + expect((await service.select(selectionInput)).grant.id).toBe(account.grantId); + }); + + it("creates and hires agents with an authorized restricted shared connection", async () => { + const { agentRoutes } = await import("../routes/agents.js"); + await db.update(companies).set({ requireBoardApprovalForNewAgents: false }).where(eq(companies.id, companyId)); + const account = await service.save(companyId, "alice", { provider: "anthropic", method: "api_key", ownership: "shared", name: "Shared creation routes", apiKey: "fixture", agentIds: [], allAgents: false }, "fixture-create-routes"); + const selected = { ...binding, mode: "shared", ...account } as const; + const app = express(); + app.use(express.json()); + app.use((req, _res, next) => { + req.actor = { type: "board", source: "local_implicit", userId: "alice", companyIds: [companyId] }; + next(); + }); + app.use("/api", agentRoutes(db)); + app.use((error: { status?: number; message: string }, _req: express.Request, res: express.Response, _next: express.NextFunction) => { res.status(error.status ?? 500).json({ error: error.message }); }); + for (const endpoint of ["agents", "agent-hires"]) { + const response = await request(app).post(`/api/companies/${companyId}/${endpoint}`).send({ name: `Shared ${endpoint}`, role: "general", adapterType: "claude_local", adapterConfig: { model: "claude-sonnet-4-6" }, runtimeConfig: { aiConnection: selected } }); + expect(response.status, JSON.stringify(response.body)).toBe(201); + const agent = endpoint === "agents" ? response.body : response.body.agent; + expect(agent.adapterConfig.model).toBe("claude-sonnet-4-6"); + expect(agent.runtimeConfig.aiConnection).toEqual(selected); + const installs = await db.select().from(toolConnectionInstalls).where(and(eq(toolConnectionInstalls.connectionId, account.connectionId), eq(toolConnectionInstalls.targetId, agent.id))); + expect(installs).toHaveLength(1); + expect((await service.select({ ...input, agentId: agent.id, userId: "alice", binding: selected })).grant.id).toBe(account.grantId); + } + // A database failure between the two inserts must roll back the agent too. + await db.execute(sql`CREATE FUNCTION reject_test_ai_install() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN RAISE EXCEPTION 'fixture install failure'; END $$`); + await db.execute(sql`CREATE TRIGGER reject_test_ai_install BEFORE INSERT ON tool_connection_installs FOR EACH ROW EXECUTE FUNCTION reject_test_ai_install()`); + try { + for (const endpoint of ["agents", "agent-hires"]) { + const name = `Rollback ${endpoint}`; + const response = await request(app).post(`/api/companies/${companyId}/${endpoint}`).send({ name, role: "general", adapterType: "claude_local", adapterConfig: { model: "claude-sonnet-4-6" }, runtimeConfig: { aiConnection: selected } }); + expect(response.status).toBe(500); + expect(await db.select().from(agents).where(and(eq(agents.companyId, companyId), eq(agents.name, name)))).toEqual([]); + } + } finally { + await db.execute(sql`DROP TRIGGER reject_test_ai_install ON tool_connection_installs`); + await db.execute(sql`DROP FUNCTION reject_test_ai_install()`); + } + }, 30000); + +}); + + +describe("AI connection recovery delivery", () => { + it.each(["restored", "newer failure", "different blocker", "revoked again", "closed task"])( + "continues only the repaired source failure: %s", async (scenario) => { + const userId = `recovery-${randomUUID()}`; + const recoveringAgentId = randomUUID(); + const issueId = randomUUID(); + const failedRunId = randomUUID(); + await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: userId, status: "active", membershipRole: "member" }); + await db.insert(agents).values({ id: recoveringAgentId, companyId, name: "Recovery agent", status: "active", adapterType: "claude_local", runtimeConfig: { aiConnection: binding } }); + await db.insert(issues).values({ id: issueId, companyId, title: "Restore selected account", status: "in_progress", assigneeAgentId: recoveringAgentId }); + await db.insert(heartbeatRuns).values({ id: failedRunId, companyId, agentId: recoveringAgentId, status: "running", responsibleUserId: userId, contextSnapshot: { issueId } }); + const intents = connectionIntentService(db); + const pending = await intents.request({ sub: recoveringAgentId, company_id: companyId, run_id: failedRunId, responsible_user_id: userId }, "anthropic", { purpose: "ai" }); + const account = await create(userId, `Recovered ${scenario}`); + expect((await intents.setupOptions(pending.interactionId!)).existingConnections.map(connection => connection.id)).toEqual([account.connectionId]); + await db.update(heartbeatRuns).set({ status: "failed", errorCode: "configuration_incomplete", resultJson: { configurationIncomplete: { reason: "ai_connection_unavailable" } } }).where(eq(heartbeatRuns.id, failedRunId)); + await db.update(issues).set({ status: "blocked" }).where(eq(issues.id, issueId)); + await issueRecoveryActionService(db).upsertSourceScoped({ companyId, sourceIssueId: issueId, kind: "configuration_validation", cause: "configuration_incomplete", fingerprint: `ai:${issueId}`, nextAction: "Reconnect", ownerType: "board", evidence: { latestRunId: failedRunId } }); + await intents.complete(pending.interactionId!, account.connectionId, userId); + if (scenario === "newer failure") await db.insert(heartbeatRuns).values({ companyId, agentId: recoveringAgentId, status: "failed", contextSnapshot: { issueId }, createdAt: new Date(Date.now() + 1000) }); + if (scenario === "different blocker") await db.update(issueRecoveryActions).set({ cause: "workspace_validation_failed" }).where(eq(issueRecoveryActions.sourceIssueId, issueId)); + if (scenario === "closed task") await db.update(issues).set({ status: "done" }).where(eq(issues.id, issueId)); + if (scenario === "revoked again") { + await toolAccessService(db).revokeConnectionGrant(account.connectionId, account.grantId, { actorType: "user", actorId: userId }); + const repairOptions = await intents.setupOptions(pending.interactionId!); + expect(repairOptions.existingConnections).toEqual([]); + expect(repairOptions.aiRepair).toMatchObject({ canReconnect: true, connection: { id: account.connectionId, grantId: account.grantId, isDefault: true, status: "revoked" } }); + } + const wakeup = vi.fn(async (_agentId, opts) => { + await db.insert(agentWakeupRequests).values({ companyId, agentId: recoveringAgentId, source: "automation", status: "queued", idempotencyKey: opts.idempotencyKey }); + return null; + }); + const delivery = connectionIntentDeliveryService(db, { wakeup } as never); + await delivery.deliver(pending.interactionId!); + await delivery.deliver(pending.interactionId!); + const [issue] = await db.select().from(issues).where(eq(issues.id, issueId)); + if (scenario === "restored") { + expect(issue.status).toBe("in_progress"); + expect(wakeup).toHaveBeenCalledTimes(1); + expect(wakeup).toHaveBeenCalledWith(recoveringAgentId, expect.objectContaining({ contextSnapshot: expect.objectContaining({ forceFreshSession: true }) })); + expect(await issueRecoveryActionService(db).getActiveForIssue(companyId, issueId)).toBeNull(); + const [receipt] = await db.select().from(connectionIntentDeliveries).where(eq(connectionIntentDeliveries.interactionId, pending.interactionId!)); + expect(receipt.deliveredAt).not.toBeNull(); + } else { + expect(wakeup).not.toHaveBeenCalled(); + expect(issue.status).toBe(scenario === "closed task" ? "done" : "blocked"); + } + }, 30000, + ); +}); diff --git a/server/src/__tests__/ai-legacy-compatibility.test.ts b/server/src/__tests__/ai-legacy-compatibility.test.ts new file mode 100644 index 0000000000..bc41cd3181 --- /dev/null +++ b/server/src/__tests__/ai-legacy-compatibility.test.ts @@ -0,0 +1,175 @@ +import { beforeAll, afterAll, it, expect, vi } from "vitest"; +import { randomUUID } from "node:crypto"; +import { mkdtemp, readFile, writeFile, mkdir, rm } from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { eq, sql } from "drizzle-orm"; +import { createDb, companies, agents, companyMemberships, adapterAuthSessions, environments, connectionGrants, toolConnections, activityLog } from "@paperclipai/db"; +import { startEmbeddedPostgresTestDatabase } from "@paperclipai/db/test-embedded-postgres"; +import { secretService } from "../services/secrets.js"; +import { aiConnectionService } from "../services/ai-connections.js"; +import { prepareManagedAiRuntime } from "../services/ai-connection-runtime.js"; +import { localAiLoginService } from "../services/local-ai-login.js"; +import { readVerifiedLocalAiCredential } from "../services/local-ai-credentials.js"; +import { resolvePaperclipInstanceRoot } from "../home-paths.js"; + +let database: Awaited>; +let db: ReturnType; +let home: string; +const companyId = randomUUID(), agentId = randomUUID(), owner = "audit-owner"; +beforeAll(async () => { + home = await mkdtemp(path.join(os.tmpdir(), "ai-legacy-audit-")); + vi.stubEnv("PAPERCLIP_HOME", home); + database = await startEmbeddedPostgresTestDatabase("ai-legacy-audit-db-"); + db = createDb(database.connectionString); + await db.insert(companies).values({ id: companyId, name: "Audit", issuePrefix: "AUD" }); + await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "owner" }); + await db.insert(environments).values({ name: "Local audit", driver: "local" }).onConflictDoNothing(); + await db.insert(agents).values({ id: agentId, companyId, name: "Unadopted legacy agent", adapterType: "claude_local" }); +}, 90000); +afterAll(async () => { await database?.cleanup(); vi.unstubAllEnvs(); vi.unstubAllGlobals(); if (home) await rm(home, { recursive: true, force: true }); }); + +it("reconnect detaches an indexed credential without changing unadopted legacy agents", async () => { + const vault = secretService(db), service = aiConnectionService(db); + const definition = await vault.createUserSecretDefinition(companyId, { key: "audit_legacy_claude", name: "Legacy key", provider: "local_encrypted" }, { userId: owner }); + const secret = await vault.createCurrentUserSecretValue(companyId, owner, { definitionId: definition.id, value: "fixture-original" }, { userId: owner }); + const legacyConfig = { env: { ANTHROPIC_API_KEY: { type: "user_secret_ref", key: definition.key, required: true } } }; + await db.update(agents).set({ adapterConfig: legacyConfig }).where(eq(agents.id, agentId)); + await vault.syncUserSecretDeclarationsForTarget(companyId, { targetType: "agent", targetId: agentId }, [{ definitionKey: definition.key, configPath: "env.ANTHROPIC_API_KEY", envKey: "ANTHROPIC_API_KEY", required: true }]); + const migration = await readFile(new URL("../../../packages/db/src/migrations/0276_hard_mandroid.sql", import.meta.url), "utf8"); + await db.execute(sql.raw(migration.slice(migration.indexOf("DO $$", migration.indexOf("-- Only declared"))))); + const connection = (await service.list(companyId, owner)).find(c => c.name === secret.name)!; + const resolve = () => vault.resolveUserSecretValue(companyId, { definitionId: definition.id, responsibleUserId: owner, required: true, version: "latest" }, { companyId, responsibleUserId: owner, actorType: "system" }); + expect((await resolve())?.value).toBe("fixture-original"); + await service.save(companyId, owner, { provider: "anthropic", method: "api_key", name: connection.name, ownership: "personal", agentIds: [], allAgents: true, connectionId: connection.id, apiKey: "fixture-new-account" }, "fixture-new-account"); + expect((await resolve())?.value).toBe("fixture-original"); + const [grant] = await db.select().from(connectionGrants).where(eq(connectionGrants.id, connection.grantId)); + expect(grant.credentialSecretRefs[0].secretId).not.toBe(secret.id); + const selected = await service.select({ companyId, agentId, userId: owner, adapterType: "claude_local", binding: { provider: "anthropic", method: "api_key", mode: "responsible_user" } }); + expect(await service.credential(selected)).toBe("fixture-new-account"); + await service.save(companyId, owner, { provider: "anthropic", method: "api_key", name: connection.name, ownership: "personal", agentIds: [], allAgents: true, connectionId: connection.id, apiKey: "fixture-second" }, "fixture-second"); + expect((await resolve())?.value).toBe("fixture-original"); + const [agent] = await db.select().from(agents).where(eq(agents.id, agentId)); + expect(agent.runtimeConfig.aiConnection).toBeUndefined(); + expect(agent.adapterConfig).toEqual(legacyConfig); +}); + +const intent = { provider: "openai", method: "subscription", name: "Isolated Codex", ownership: "personal", agentIds: [], allAgents: true } as const; +const loginIntent = () => ({ ...intent, agentIds: [] }); +const auth = (mark: string, hour = 10) => JSON.stringify({ tokens: { account_id: "fixture-account", id_token: `id-${mark}`, access_token: `access-${mark}`, refresh_token: `refresh-${mark}` }, last_refresh: `2026-09-10T${hour}:00:00Z` }); +const directoryFor = (id: string) => path.join(resolvePaperclipInstanceRoot(), "ai-local-logins", id); + +it("isolates sign-in and refresh from the host, survives restart, and completes only once", async () => { + const hostHome = path.join(home, "host-codex"); + await mkdir(hostHome); + await writeFile(path.join(hostHome, "auth.json"), auth("legacy")); + vi.stubEnv("CODEX_HOME", hostHome); + vi.stubGlobal("fetch", vi.fn(async () => new Response("{}"))); + const login = localAiLoginService(db); + const attempt = await login.start(companyId, owner, loginIntent()); + const directory = directoryFor(attempt.sessionId); + expect(await localAiLoginService(db).start(companyId, owner, loginIntent())).toEqual(attempt); + expect(attempt.command).toContain(`(export CODEX_HOME='${directory}' && mkdir -p "$CODEX_HOME" && codex`); + expect(await readFile(path.join(directory, "config.toml"), "utf8")).toContain('cli_auth_credentials_store = "file"'); + expect(await login.check(companyId, owner, loginIntent(), attempt.sessionId)).toEqual({ status: "sign_in_required" }); + // Resuming a valid attempt also repairs a missing directory without changing its ID. + await rm(directory, { recursive: true }); + expect(await login.start(companyId, owner, loginIntent())).toEqual(attempt); + expect(await readFile(path.join(directory, "config.toml"), "utf8")).toContain('cli_auth_credentials_store = "file"'); + // A valid host login cannot satisfy an unfinished connection-specific login. + await expect(login.complete(companyId, owner, attempt.sessionId, loginIntent())).rejects.toThrow("sign-in command shown"); + expect((await aiConnectionService(db).list(companyId, owner)).filter(c => c.provider === "openai")).toHaveLength(0); + await writeFile(path.join(directory, "auth.json"), auth("independent-login")); + expect(await login.check(companyId, owner, loginIntent(), attempt.sessionId)).toEqual({ status: "ready" }); + expect((await aiConnectionService(db).list(companyId, owner)).filter(c => c.provider === "openai")).toHaveLength(0); + await expect(login.check(companyId, "another-owner", loginIntent(), attempt.sessionId)).rejects.toThrow("not found"); + await expect(login.check(randomUUID(), owner, loginIntent(), attempt.sessionId)).rejects.toThrow("not found"); + await expect(login.check(companyId, owner, { ...loginIntent(), ownership: "shared" }, attempt.sessionId)).rejects.toThrow("not found"); + // New service instance simulates process restart: all intent is durable. + const results = await Promise.all([ + localAiLoginService(db).complete(companyId, owner, attempt.sessionId, loginIntent()), + localAiLoginService(db).complete(companyId, owner, attempt.sessionId, loginIntent()), + ]); + expect(results[0]).toEqual(results[1]); + await expect(readFile(path.join(directory, "auth.json"))).rejects.toHaveProperty("code", "ENOENT"); + const input = { companyId, agentId, adapterType: "codex_local", responsibleUserId: owner, binding: { provider: "openai", method: "subscription", mode: "responsible_user" } as const, config: { cwd: home, model: "unchanged-model" } }; + const run = await prepareManagedAiRuntime(db, input); + const credentialFile = path.join(String(run.config.env.CODEX_HOME), "auth.json"); + expect(JSON.parse(await readFile(credentialFile, "utf8")).tokens.refresh_token).toBe("refresh-independent-login"); + await writeFile(credentialFile, auth("rotated-independent", 11)); + await run.cleanup(); + expect(await readFile(path.join(hostHome, "auth.json"), "utf8")).toBe(auth("legacy")); + const next = await prepareManagedAiRuntime(db, input); + expect(JSON.parse(await readFile(path.join(String(next.config.env.CODEX_HOME), "auth.json"), "utf8")).tokens.refresh_token).toBe("refresh-rotated-independent"); + expect(next.config.model).toBe(input.config.model); + await next.cleanup(); + // Cancellation after successful completion must not revoke the saved account. + await login.cancel(companyId, owner, attempt.sessionId); + expect((await aiConnectionService(db).list(companyId, owner)).find(c => c.id === results[0].connectionId)?.status).toBe("connected"); + expect((await db.select().from(activityLog).where(eq(activityLog.entityId, attempt.sessionId))) + .filter(event => event.action === "ai_connection.local_login_cancelled")).toHaveLength(0); + const reconnectIntent = { ...loginIntent(), connectionId: results[0].connectionId }; + const reconnect = await login.start(companyId, owner, reconnectIntent); + expect(reconnect.sessionId).not.toBe(attempt.sessionId); + await writeFile(path.join(directoryFor(reconnect.sessionId), "auth.json"), auth("new-login")); + expect(await login.complete(companyId, owner, reconnect.sessionId, reconnectIntent)).toEqual(results[0]); + expect(await readFile(path.join(hostHome, "auth.json"), "utf8")).toBe(auth("legacy")); +}); + +it("enforces local attempt ownership, company, target, cancellation, and expiry", async () => { + const login = localAiLoginService(db); + const attempt = await login.start(companyId, owner, loginIntent()); + const cancellations = async () => (await db.select().from(activityLog).where(eq(activityLog.entityId, attempt.sessionId))) + .filter(event => event.action === "ai_connection.local_login_cancelled"); + await expect(login.complete(companyId, "another-owner", attempt.sessionId, loginIntent())).rejects.toThrow("not found"); + await expect(login.cancel(companyId, "another-owner", attempt.sessionId)).rejects.toThrow("not found"); + await expect(login.cancel(randomUUID(), owner, attempt.sessionId)).rejects.toThrow("not found"); + await expect(login.complete(companyId, owner, attempt.sessionId, { ...loginIntent(), ownership: "shared" })).rejects.toThrow("not found"); + expect(await cancellations()).toHaveLength(0); + await login.cancel(companyId, owner, attempt.sessionId); + await login.cancel(companyId, owner, attempt.sessionId); + expect(await cancellations()).toEqual([expect.objectContaining({ + companyId, actorType: "user", actorId: owner, entityType: "adapter_auth_session", + entityId: attempt.sessionId, details: { provider: "openai" }, + })]); + await expect(login.complete(companyId, owner, attempt.sessionId, loginIntent())).rejects.toThrow("cancelled"); + const retry = await login.start(companyId, owner, loginIntent()); + await writeFile(path.join(directoryFor(retry.sessionId), "auth.json"), auth("abandoned")); + await db.update(adapterAuthSessions).set({ expiresAt: new Date(Date.now() - 1000) }).where(eq(adapterAuthSessions.id, retry.sessionId)); + await expect(login.complete(companyId, owner, retry.sessionId, loginIntent())).rejects.toThrow("expired"); + expect(await login.check(companyId, owner, loginIntent(), retry.sessionId)).toEqual({ status: "expired" }); + await localAiLoginService(db).reapExpired(); + await expect(readFile(path.join(directoryFor(retry.sessionId), "auth.json"))).rejects.toHaveProperty("code", "ENOENT"); + const [row] = await db.select().from(adapterAuthSessions).where(eq(adapterAuthSessions.id, retry.sessionId)); + expect(row.status).toBe("timed_out"); +}); + +it("explicit retry replaces another owned local attempt while ordinary navigation preserves it", async () => { + const login = localAiLoginService(db); + const first = await login.start(companyId, owner, loginIntent()); + const restricted = { ...loginIntent(), allAgents: false, agentIds: [agentId] }; + await expect(login.start(companyId, owner, restricted)).rejects.toThrow("Another sign-in"); + const retry = await login.start(companyId, owner, restricted, true); + expect(retry.sessionId).not.toBe(first.sessionId); + const [old] = await db.select().from(adapterAuthSessions).where(eq(adapterAuthSessions.id, first.sessionId)); + expect(old.status).toBe("cancelled"); + await expect(readFile(path.join(directoryFor(first.sessionId), "config.toml"))).rejects.toHaveProperty("code", "ENOENT"); + expect(await login.start(companyId, owner, restricted)).toEqual(retry); + await login.cancel(companyId, owner, retry.sessionId); +}); + +it("blocks preview-era copied subscriptions until isolated reconnect, leaving legacy config intact", async () => { + const service = aiConnectionService(db); + const account = (await service.list(companyId, owner)).find(c => c.provider === "openai")!; + const [connection] = await db.select().from(toolConnections).where(eq(toolConnections.id, account.id)); + await db.update(toolConnections).set({ config: { ...connection.config, aiIsolatedSubscription: false } }).where(eq(toolConnections.id, account.id)); + await expect(prepareManagedAiRuntime(db, { + companyId, agentId, adapterType: "codex_local", responsibleUserId: owner, + binding: { provider: "openai", method: "subscription", mode: "responsible_user" }, config: { cwd: home }, + })).rejects.toThrow("separate sign-in"); + expect((await service.list(companyId, owner)).find(c => c.id === account.id)?.status).toBe("needs_attention"); + const [agent] = await db.select().from(agents).where(eq(agents.id, agentId)); + expect(agent.runtimeConfig.aiConnection).toBeUndefined(); + const [grant] = await db.select().from(connectionGrants).where(eq(connectionGrants.id, account.grantId)); + expect(grant.status).toBe("active"); +}); diff --git a/server/src/__tests__/authorization-service.test.ts b/server/src/__tests__/authorization-service.test.ts index 7a303268f4..b76683562e 100644 --- a/server/src/__tests__/authorization-service.test.ts +++ b/server/src/__tests__/authorization-service.test.ts @@ -1462,8 +1462,8 @@ describeEmbeddedPostgres("authorization service", () => { })).resolves.toMatchObject({ allowed: false, reason: "deny_missing_membership" }); }); - it("keeps denying self-gated null-mapped actions for board members", async () => { - const company = await createCompany(db, "BoardWakeDenied"); + it("allows legacy member roles to wake agents while rejecting incomplete task mutation scope", async () => { + const company = await createCompany(db, "BoardWake"); const userId = `user-${randomUUID()}`; const targetAgent = await createAgent(db, company.id, { role: "engineer" }); await db.insert(companyMemberships).values({ @@ -1481,8 +1481,8 @@ describeEmbeddedPostgres("authorization service", () => { action: "agent:wake", resource: { type: "agent", companyId: company.id, agentId: targetAgent.id }, })).resolves.toMatchObject({ - allowed: false, - reason: "deny_unsupported_action", + allowed: true, + reason: "allow_simple_company_member", }); const issue = await createIssue(db, company.id, { title: "Wake denied issue" }); await expect(authorization.decide({ @@ -1749,6 +1749,25 @@ describeEmbeddedPostgres("authorization service", () => { }); }); + it.each(["session", "cloud_tenant"] as const)("allows %s operators to start agents, without granting hiring rights", async (source) => { + const company = await createCompany(db, "wake"); + const agent = await createAgent(db, company.id); + const userId = await createUser(db); + await db.insert(companyMemberships).values({ companyId: company.id, + principalType: "user", principalId: userId, status: "active", membershipRole: "operator" }); + const auth = authorizationService(db); + const actor = { type: "board" as const, source, userId, companyIds: [company.id] }; + const resource = { type: "agent" as const, companyId: company.id, agentId: agent.id }; + expect(await auth.decide({ actor, action: "agent:wake", resource })).toMatchObject({ allowed: true }); + expect(await auth.decide({ actor, action: "agents:create", resource: { type: "company", companyId: company.id } })).toMatchObject({ allowed: false }); + await db.update(companyMemberships).set({ membershipRole: "viewer" }).where(eq(companyMemberships.principalId, userId)); + expect(await auth.decide({ actor, action: "agent:wake", resource })).toMatchObject({ allowed: false }); + await db.update(companyMemberships).set({ membershipRole: "operator", status: "suspended" }).where(eq(companyMemberships.principalId, userId)); + expect(await auth.decide({ actor, action: "agent:wake", resource })).toMatchObject({ allowed: false }); + const otherCompany = await createCompany(db, "other-wake"); + expect(await auth.decide({ actor, action: "agent:wake", resource: { ...resource, companyId: otherCompany.id } })).toMatchObject({ allowed: false }); + }); + it("limits viewer members to read-only visibility actions", async () => { const company = await createCompany(db, "BoardViewerVisibility"); const userId = `user-${randomUUID()}`; diff --git a/server/src/__tests__/company-search-service.test.ts b/server/src/__tests__/company-search-service.test.ts index 0db84ccced..a8665665ab 100644 --- a/server/src/__tests__/company-search-service.test.ts +++ b/server/src/__tests__/company-search-service.test.ts @@ -163,6 +163,19 @@ describeEmbeddedPostgres("companySearchService", () => { return id; } + it("keeps exact entity names ahead of speculative task typos and rejects empty quotes", async () => { + const companyId = await createCompany(); + const agentId = await createAgent(companyId, { name: "Mibile" }); + const projectId = await createProject(companyId, { name: "Mibile" }); + const taskId = await createIssue(companyId, { title: "Mobile navigation" }); + const result = await svc.search(companyId, companySearchQuerySchema.parse({ q: "mibile" })); + const ids = result.results.map((row) => row.id); + expect(ids).toContain(taskId); + expect(ids.indexOf(agentId)).toBeLessThan(ids.indexOf(taskId)); + expect(ids.indexOf(projectId)).toBeLessThan(ids.indexOf(taskId)); + expect((await svc.search(companyId, companySearchQuerySchema.parse({ q: '""' }))).results).toEqual([]); + }); + it("ranks exact issue identifiers before weaker title matches", async () => { const companyId = await createCompany(); const exactId = await createIssue(companyId, { @@ -180,7 +193,7 @@ describeEmbeddedPostgres("companySearchService", () => { expect(result.results[0]?.matchedFields).toContain("identifier"); }); - it("ranks phrase and all-token issue matches before partial scattered-token matches", async () => { + it("ranks phrase before reordered title words and rejects partial matches", async () => { const companyId = await createCompany(); const base = new Date("2026-01-01T00:00:00.000Z").getTime(); const partialTokenId = await createIssue(companyId, { @@ -201,7 +214,8 @@ describeEmbeddedPostgres("companySearchService", () => { const result = await svc.search(companyId, companySearchQuerySchema.parse({ q: "alpha beta", scope: "issues" })); - expect(result.results.map((row) => row.id)).toEqual([phraseId, allTokenId, partialTokenId]); + expect(result.results.map((row) => row.id)).toEqual([phraseId, allTokenId]); + expect(result.results.map((row) => row.id)).not.toContain(partialTokenId); }); it("matches multiple tokens across the same issue thread and returns comment snippets", async () => { @@ -682,6 +696,83 @@ describeEmbeddedPostgres("companySearchService", () => { } }); + it("does not interpret short UI terms as the middle of unrelated words", async () => { + const companyId = await createCompany(); + const target = await createIssue(companyId, { title: "Improve mobile UI" }); + await createIssue(companyId, { title: "Build billing reports" }); + const result = await svc.search(companyId, companySearchQuerySchema.parse({ q: "UI" })); + expect(result.results.map((row) => row.id)).toEqual([target]); + }); + + it("keeps typo fallback inside the requested filters", async () => { + const companyId = await createCompany(); + await createIssue(companyId, { title: "Mibile API", status: "done" }); + const target = await createIssue(companyId, { title: "Mobile API", status: "todo" }); + const result = await svc.search(companyId, companySearchQuerySchema.parse({ q: "mibile api", status: "todo" })); + expect(result.results.map((row) => row.id)).toEqual([target]); + }); + + it("keeps exact title hits on the task and chooses the most complete context evidence", async () => { + const companyId = await createCompany(); + const task = await createIssue(companyId, { title: "Aurora callback" }); + await db.insert(issueComments).values({ companyId, issueId: task, body: "Aurora callback is mentioned here too." }); + const exact = await svc.search(companyId, companySearchQuerySchema.parse({ q: "Aurora callback" })); + expect(exact.results[0]?.href).not.toContain("#comment-"); + + const holder = await createIssue(companyId, { title: "Connection investigation" }); + await db.insert(issueComments).values({ companyId, issueId: holder, body: "Aurora was discussed.", updatedAt: new Date("2026-06-01") }); + const strongest = randomUUID(); + await db.insert(issueComments).values({ id: strongest, companyId, issueId: holder, + body: "Aurora loses the callback state.", updatedAt: new Date("2026-01-01") }); + const result = await svc.search(companyId, companySearchQuerySchema.parse({ q: "aurora state callback" })); + expect(result.results[0]?.href).toContain(`#comment-${strongest}`); + expect(result.results[0]?.snippet).toContain("state"); + }); + + it.each(["comment", "document"] as const)("preserves %s evidence when title and identifier both match", async (source) => { + const companyId = await createCompany(); + const task = await createIssue(companyId, { + identifier: "CTX-123", title: "CTX callback investigation", description: "CTX callback details", + }); + const sourceId = randomUUID(); + if (source === "comment") { + await db.insert(issueComments).values({ id: sourceId, companyId, issueId: task, body: "The missing signal is quasar." }); + } else { + await db.insert(documents).values({ id: sourceId, companyId, title: "Investigation plan", latestBody: "The missing signal is quasar.", format: "markdown" }); + await db.insert(issueDocuments).values({ companyId, issueId: task, documentId: sourceId, key: "plan" }); + } + + const result = await svc.search(companyId, companySearchQuerySchema.parse({ q: "CTX quasar" })); + const match = result.results.find((row) => row.id === task)!; + expect(match.matchedFields).toEqual(expect.arrayContaining(["identifier", "title", source])); + expect(match.score).toBeGreaterThanOrEqual(2000); + expect(match.score).toBeLessThan(3000); + expect(match.snippets).toHaveLength(2); + expect(match.snippets[0]).toMatchObject({ field: source, text: expect.stringContaining("quasar") }); + expect(match.snippet).toContain("quasar"); + expect(match.href).toContain(source === "comment" ? `#comment-${sourceId}` : "#document-plan"); + }); + + it("reflects edits, deleted comments and document updates immediately", async () => { + const companyId = await createCompany(); + const task = await createIssue(companyId, { title: "Old uniquequartz title" }); + const find = () => svc.search(companyId, companySearchQuerySchema.parse({ q: '"uniquequartz"' })); + expect((await find()).results.map((row) => row.id)).toEqual([task]); + await db.update(issues).set({ title: "New title" }).where(sql`${issues.id} = ${task}`); + expect((await find()).results).toEqual([]); + const comment = randomUUID(); + await db.insert(issueComments).values({ id: comment, companyId, issueId: task, body: "uniquequartz" }); + expect((await find()).results.map((row) => row.id)).toEqual([task]); + await db.update(issueComments).set({ deletedAt: new Date() }).where(sql`${issueComments.id} = ${comment}`); + expect((await find()).results).toEqual([]); + const doc = randomUUID(); + await db.insert(documents).values({ id: doc, companyId, title: "Findings", latestBody: "uniquequartz", format: "markdown" }); + await db.insert(issueDocuments).values({ companyId, issueId: task, documentId: doc, key: "plan" }); + expect((await find()).results.map((row) => row.id)).toEqual([task]); + await db.update(documents).set({ latestBody: "Updated findings" }).where(sql`${documents.id} = ${doc}`); + expect((await find()).results).toEqual([]); + }); + it("uses pg_trgm for conservative fuzzy title matches", async () => { const companyId = await createCompany(); const issueId = await createIssue(companyId, { diff --git a/server/src/__tests__/connection-intents-service.test.ts b/server/src/__tests__/connection-intents-service.test.ts index a940882963..d3cb60829a 100644 --- a/server/src/__tests__/connection-intents-service.test.ts +++ b/server/src/__tests__/connection-intents-service.test.ts @@ -3,6 +3,7 @@ import { and, eq, sql } from "drizzle-orm"; import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; import { agents, + aiConnectionDefaults, agentWakeupRequests, issueComments, companies, @@ -792,4 +793,33 @@ describeEmbeddedPostgres("connectionIntentService", () => { await expect(service.search(claims, "notion")) .rejects.toThrow("no longer active"); }); + it("keeps runtime authentication requests distinct from the same provider's tool requests", async () => { + const companyId = claims.company_id; + const agentId = randomUUID(); + const issueId = randomUUID(); + const aiRunId = randomUUID(); + const binding = { provider: "anthropic", method: "api_key", mode: "responsible_user" } as const; + await db.insert(agents).values({ id: agentId, companyId, name: "AI Agent", adapterType: "claude_local", runtimeConfig: { aiConnection: binding } }); + await db.insert(issues).values({ id: issueId, companyId, title: "AI authentication", status: "in_progress", assigneeAgentId: agentId }); + await db.insert(heartbeatRuns).values({ id: aiRunId, companyId, agentId, status: "running", responsibleUserId: claims.responsible_user_id, contextSnapshot: { issueId } }); + const [app] = await db.insert(toolApplications).values({ companyId, applicationKey: "ai-intent-fixture", name: "AI intent fixture", type: "mcp_http", metadata: { sourceTemplateKey: "anthropic" } }).returning(); + const [connection] = await db.insert(toolConnections).values({ companyId, applicationId: app!.id, name: "Personal Claude API", uid: `ai-${randomUUID()}`, connectionPurpose: "ai", transport: "runtime_auth", authKind: "api_key", credentialPolicy: "per_user", healthStatus: "ok", status: "active", enabled: true, config: { sourceTemplateKey: "anthropic", ai: { provider: "anthropic", method: "api_key" } } }).returning(); + const [grant] = await db.insert(connectionGrants).values({ companyId, connectionId: connection!.id, kind: "user", subjectUserId: claims.responsible_user_id, createdByUserId: claims.responsible_user_id }).returning(); + await db.insert(aiConnectionDefaults).values({ companyId, userId: claims.responsible_user_id!, provider: "anthropic", method: "api_key", grantId: grant!.id }); + const aiClaims = { ...claims, sub: agentId, run_id: aiRunId }; + const service = connectionIntentService(db); + const toolRequest = await service.request(aiClaims, "anthropic"); + const aiRequest = await service.request(aiClaims, "anthropic", { purpose: "ai" }); + expect(aiRequest.state).toBe("needs_user_action"); + expect(aiRequest.interactionId).not.toBe(toolRequest.interactionId); + expect((await service.setupOptions(aiRequest.interactionId!)).aiConnection).toEqual(binding); + expect((await service.setupOptions(toolRequest.interactionId!)).existingConnections).toEqual([]); + await expect(service.complete(toolRequest.interactionId!, connection!.id, claims.responsible_user_id!)).rejects.toThrow("cannot satisfy"); + await expect(service.complete(aiRequest.interactionId!, connection!.id, claims.responsible_user_id!)).resolves.toMatchObject({ status: "accepted" }); + expect((await service.request(aiClaims, "anthropic", { purpose: "ai" })).state).toBe("ready"); + expect((await service.request(aiClaims, "anthropic")).state).toBe("needs_user_action"); + expect((await service.search(aiClaims, "openrouter")).results.some(result => result.service === "openrouter")).toBe(false); + }); + + }); diff --git a/server/src/__tests__/fixtures/task-search-corpus.ts b/server/src/__tests__/fixtures/task-search-corpus.ts new file mode 100644 index 0000000000..dec251c52d --- /dev/null +++ b/server/src/__tests__/fixtures/task-search-corpus.ts @@ -0,0 +1,77 @@ +// Authored relevance judgments, independent of the ranker's scoring constants. +export const taskSearchCorpus = [ + { key: "id", identifier: "PAP-42", title: "Repair callback state", status: "done" }, + { key: "id-mention", title: "PAP-42 follow-up discussion" }, + { key: "id-neighbor", identifier: "PAP-420", title: "Repair callback state later" }, + { key: "oauth", title: "Fix GitHub OAuth callback", status: "done" }, + { key: "oauth-noise", title: "GitHub release checklist", comments: ["OAuth is mentioned in an unrelated weekly update."] }, + { key: "oauth-partial", title: "GitHub repository badges" }, + { key: "oauth-body", title: "Repair the connection flow", description: "GitHub OAuth callback loses state on redirect." }, + { key: "oauth-comment", title: "Investigate sign-in", comments: ["The GitHub OAuth callback loses state on redirect."] }, + { key: "oauth-doc", title: "Connection investigation", document: { title: "GitHub OAuth callback findings", body: "State is lost on redirect." } }, + { key: "search", title: "Improve search performance" }, + { key: "search-noise", title: "Improve exports", description: "A search for performance numbers was included in the meeting." }, + { key: "search-partial", title: "Search typography" }, + { key: "mobile", title: "Polish mobile navigation" }, + { key: "mobile-api", title: "Build mobile API" }, + { key: "mobile-ui", title: "Build mobile UI" }, + { key: "onboarding", title: "Onboarding wizard polish" }, + { key: "quoted", title: "Repair connection timeout handling" }, + { key: "quoted-scattered", title: "Connection retry after timeout" }, + { key: "cross", title: "Checkout ownership", comments: ["A concurrency race needs a regression test."] }, + { key: "cross-partial", title: "Checkout style guide" }, + { key: "document", title: "Adapter investigation", document: { title: "Hermes parser plan", body: "Discover plugins from their package manifest." } }, + { key: "percentage", title: "Release 100% checklist" }, + { key: "percentage-decoy", title: "Release 1000 checklist" }, + { key: "path", title: "Fix foo_bar lookup" }, + { key: "path-decoy", title: "Fix fooXbar lookup" }, + { key: "unicode", title: "Réparer navigation mobile" }, + { key: "identifier-code", title: "Document heartbeat_run_events retention" }, + { key: "word", title: "Fix API authentication" }, + { key: "word-decoy", title: "Capistrano migration", description: "API details appear here.", comments: ["API is an incidental mention."] }, + { key: "freshness", title: "Reconcile billing ledger", status: "done" }, + { key: "freshness-noise", title: "Weekly financial update", comments: ["Reconcile billing ledger was one of many completed projects."] }, +] as const; + +export type TaskSearchCase = { + name: string; + q: string; + relevant: Record; // 3 = intended task; 2 = useful; 1 = incidental; absent = irrelevant + first?: string; + absent?: string[]; + scope?: "all" | "issues" | "comments" | "documents"; +}; +export const taskSearchCases: TaskSearchCase[] = [ + { name: "exact identifier", q: "PAP-42", relevant: { id: 3, "id-mention": 1, "id-neighbor": 1 }, first: "id" }, + { name: "identifier case", q: "pap-42", relevant: { id: 3, "id-mention": 1, "id-neighbor": 1 }, first: "id" }, + { name: "compact identifier", q: "pap42", relevant: { id: 3, "id-neighbor": 1 }, first: "id" }, + { name: "spaced identifier", q: "PAP 42", relevant: { id: 3, "id-mention": 1, "id-neighbor": 1 }, first: "id" }, + { name: "title phrase", q: "GitHub OAuth", relevant: { oauth: 3, "oauth-body": 2, "oauth-comment": 2, "oauth-doc": 2, "oauth-noise": 1 }, first: "oauth", absent: ["oauth-partial"] }, + { name: "reordered title words", q: "OAuth GitHub callback", relevant: { oauth: 3, "oauth-body": 2, "oauth-comment": 2, "oauth-doc": 2 }, first: "oauth", absent: ["oauth-partial", "oauth-noise"] }, + { name: "title beats body chatter", q: "performance search", relevant: { search: 3, "search-noise": 1 }, first: "search", absent: ["search-partial"] }, + { name: "title beats incidental comment", q: "billing ledger", relevant: { freshness: 3, "freshness-noise": 1 }, first: "freshness" }, + { name: "filler words", q: "the GitHub OAuth callback", relevant: { oauth: 3, "oauth-body": 2, "oauth-comment": 2, "oauth-doc": 2 }, first: "oauth", absent: ["oauth-noise"] }, + { name: "quoted phrase", q: '"connection timeout"', relevant: { quoted: 3 }, first: "quoted", absent: ["quoted-scattered"] }, + { name: "quoted phrase plus term", q: 'repair "connection timeout"', relevant: { quoted: 3 }, first: "quoted", absent: ["quoted-scattered"] }, + { name: "transposition", q: "serach", relevant: { search: 3, "search-partial": 3 } }, + { name: "substitution", q: "mibile navigation", relevant: { mobile: 3, unicode: 3 }, absent: ["mobile-api", "mobile-ui"] }, + { name: "two missing letters", q: "onbordng wizard", relevant: { onboarding: 3 }, first: "onboarding" }, + { name: "short token constrains typo", q: "mibile api", relevant: { "mobile-api": 3 }, first: "mobile-api", absent: ["mobile", "mobile-ui"] }, + { name: "cross-field thread", q: "checkout concurrency", relevant: { cross: 3 }, first: "cross", absent: ["cross-partial"] }, + { name: "document title", q: "Hermes parser", relevant: { document: 3 }, first: "document" }, + { name: "document body", q: "plugins manifest", relevant: { document: 3 }, first: "document" }, + { name: "literal percent", q: "100%", relevant: { percentage: 3 }, first: "percentage", absent: ["percentage-decoy"] }, + { name: "literal underscore", q: "foo_bar", relevant: { path: 3 }, first: "path", absent: ["path-decoy"] }, + { name: "code identifier", q: "heartbeat_run_events", relevant: { "identifier-code": 3 }, first: "identifier-code" }, + { name: "unicode", q: "réparer mobile", relevant: { unicode: 3 }, first: "unicode" }, + { name: "whole word title", q: "api", relevant: { word: 3, "mobile-api": 3, "word-decoy": 1 } }, + { name: "no result", q: "quasarxylophone", relevant: {} }, +]; + +export function searchQualityMetrics(keys: string[], relevant: Record) { + const gain = (grade: number, index: number) => (2 ** grade - 1) / Math.log2(index + 2); + const dcg = keys.slice(0, 5).reduce((sum, key, index) => sum + gain(relevant[key] ?? 0, index), 0); + const ideal = Object.values(relevant).sort((a, b) => b - a).slice(0, 5).reduce((sum, grade, index) => sum + gain(grade, index), 0); + const rank = keys.findIndex((key) => relevant[key] === 3); + return { ndcg5: ideal === 0 ? Number(keys.length === 0) : dcg / ideal, reciprocalRank: rank < 0 ? 0 : 1 / (rank + 1) }; +} diff --git a/server/src/__tests__/health-dev-server-token.test.ts b/server/src/__tests__/health-dev-server-token.test.ts index 4f96249ea8..d9b1f8ddfd 100644 --- a/server/src/__tests__/health-dev-server-token.test.ts +++ b/server/src/__tests__/health-dev-server-token.test.ts @@ -106,6 +106,7 @@ describe("GET /health dev-server supervisor access", () => { status: "ok", deploymentMode: "authenticated", deploymentExposure: "private", + localAiLoginSupported: true, commit: null, bootstrapStatus: "ready", bootstrapInviteActive: false, diff --git a/server/src/__tests__/health.test.ts b/server/src/__tests__/health.test.ts index 2e5faaa77c..1d97f90e0f 100644 --- a/server/src/__tests__/health.test.ts +++ b/server/src/__tests__/health.test.ts @@ -343,6 +343,7 @@ describe("GET /health", () => { status: "ok", deploymentMode: "authenticated", deploymentExposure: "public", + localAiLoginSupported: false, commit: testServerInfo.git.fullSha, bootstrapStatus: "ready", bootstrapInviteActive: false, @@ -400,6 +401,7 @@ describe("GET /health", () => { status: "ok", deploymentMode: "authenticated", deploymentExposure: "public", + localAiLoginSupported: false, commit: testServerInfo.git.fullSha, bootstrapStatus: "ready", bootstrapInviteActive: false, @@ -438,6 +440,7 @@ describe("GET /health", () => { status: "ok", deploymentMode: "authenticated", deploymentExposure: "public", + localAiLoginSupported: false, commit: testServerInfo.git.fullSha, bootstrapStatus: "ready", bootstrapInviteActive: false, diff --git a/server/src/__tests__/heartbeat-responsible-user-invariant.test.ts b/server/src/__tests__/heartbeat-responsible-user-invariant.test.ts index 8470b456b5..0fd788d258 100644 --- a/server/src/__tests__/heartbeat-responsible-user-invariant.test.ts +++ b/server/src/__tests__/heartbeat-responsible-user-invariant.test.ts @@ -149,6 +149,142 @@ describeEmbeddedPostgres("heartbeat responsible-user invariant", () => { return { companyId, ownerUserId, agentId }; } + it("dispatches an interrupted queue under the clicking operator through the real startup path", async () => { + const { companyId, agentId, ownerUserId } = await seedCompany(); + const operatorId = `operator-${randomUUID()}`, issueId = randomUUID(), commentId = randomUUID(), queueId = randomUUID(); + await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: operatorId, + membershipRole: "operator", status: "active" }); + await db.insert(issues).values({ id: issueId, companyId, title: "Interrupted queue", status: "todo", + assigneeAgentId: agentId, responsibleUserId: ownerUserId }); + await db.insert(issueComments).values({ id: commentId, companyId, issueId, authorUserId: ownerUserId, body: "Continue the task" }); + await db.insert(agentWakeupRequests).values({ id: queueId, companyId, agentId, + source: "automation", status: "deferred_issue_execution", requestedByActorType: "system", + payload: { issueId, commentId, queuedCommentInterrupt: { actorId: operatorId, requestedAt: new Date().toISOString() }, + _paperclipWakeContext: { wakeCommentIds: [commentId], responsibleUserId: ownerUserId, + retryOfRunId: randomUUID(), originIdentityContextId: randomUUID() } }, + }); + await heartbeat.resumeQueuedCommentInterrupt(companyId, queueId); + const [receipt] = await db.select().from(agentWakeupRequests).where(eq(agentWakeupRequests.id, queueId)); + expect(receipt.status).toBe("coalesced"); + const completed = await waitForRun(db, receipt.runId!); + expect(completed).toMatchObject({ status: "succeeded", responsibleUserId: operatorId }); + expect(completed?.activeIdentityContextId).toBeTruthy(); + expect(completed?.contextSnapshot?.originIdentityContextId).toBeUndefined(); + expect(completed?.contextSnapshot?.retryOfRunId).toBeUndefined(); + expect(mockAdapterExecute).toHaveBeenCalled(); + await drainHeartbeatRunsToQuiescence(db, heartbeat); + const runs = await db.select().from(heartbeatRuns).where(eq(heartbeatRuns.companyId, companyId)); + expect(runs.every(run => run.responsibleUserId === operatorId && run.status === "succeeded")).toBe(true); + expect((await db.select().from(issueComments).where(eq(issueComments.id, commentId)))[0].authorUserId).toBe(ownerUserId); + }); + + it("keeps a board manual wake under its caller even when it adopts someone else's queue", async () => { + const { companyId, agentId, ownerUserId } = await seedCompany(); + const operatorId = `operator-${randomUUID()}`, issueId = randomUUID(), commentId = randomUUID(), queueId = randomUUID(); + await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: operatorId, + membershipRole: "operator", status: "active" }); + await db.insert(issues).values({ id: issueId, companyId, title: "Manual wake", status: "todo", + assigneeAgentId: agentId, responsibleUserId: ownerUserId }); + await db.insert(issueComments).values({ id: commentId, companyId, issueId, authorUserId: ownerUserId, body: "Pending work" }); + await db.insert(agentWakeupRequests).values({ id: queueId, companyId, agentId, + source: "automation", reason: "issue_commented", status: "deferred_issue_execution", requestedByActorType: "user", requestedByActorId: ownerUserId, + payload: { issueId, commentId, _paperclipWakeContext: { wakeCommentIds: [commentId] } }, + }); + const run = await heartbeat.wakeup(agentId, { manualUserWake: true, source: "on_demand", triggerDetail: "manual", + payload: { issueId }, requestedByActorType: "user", requestedByActorId: operatorId, + contextSnapshot: { responsibleUserId: operatorId } }); + expect(run?.responsibleUserId).toBe(operatorId); + const completed = await waitForRun(db, run!.id); + expect(completed).toMatchObject({ status: "succeeded", responsibleUserId: operatorId }); + expect(completed?.contextSnapshot?.wakeCommentIds).toEqual([commentId]); + await drainHeartbeatRunsToQuiescence(db, heartbeat); + expect((await db.select().from(heartbeatRuns)).every(row => row.responsibleUserId === operatorId)).toBe(true); + }); + + it("keeps the clicking user when a manual wake merges into an older deferred receipt", async () => { + const { companyId, agentId, ownerUserId } = await seedCompany(); + const operatorId = `operator-${randomUUID()}`, issueId = randomUUID(), commentId = randomUUID(), queueId = randomUUID(); + await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: operatorId, + membershipRole: "operator", status: "active" }); + await db.insert(issues).values({ id: issueId, companyId, title: "Deferred manual wake", status: "todo", + assigneeAgentId: agentId, responsibleUserId: ownerUserId }); + let finish!: () => void; + const blocked = new Promise(resolve => { finish = resolve; }); + const execute = mockAdapterExecute.getMockImplementation()!; + mockAdapterExecute.mockImplementationOnce(async () => { await blocked; return execute(); }); + const first = await heartbeat.wakeup(agentId, { payload: { issueId }, + requestedByActorType: "user", requestedByActorId: ownerUserId }); + try { + await vi.waitFor(() => expect(mockAdapterExecute).toHaveBeenCalled(), { timeout: 5_000 }); + await db.insert(issueComments).values({ id: commentId, companyId, issueId, authorUserId: ownerUserId, body: "Pending work" }); + await db.insert(agentWakeupRequests).values({ id: queueId, companyId, agentId, + source: "automation", reason: "issue_commented", status: "deferred_issue_execution", + requestedByActorType: "user", requestedByActorId: ownerUserId, + payload: { issueId, commentId, _paperclipWakeContext: { wakeCommentIds: [commentId] } }, + }); + expect(await heartbeat.wakeup(agentId, { manualUserWake: true, source: "on_demand", triggerDetail: "manual", + payload: { issueId }, requestedByActorType: "user", requestedByActorId: operatorId, + contextSnapshot: { responsibleUserId: operatorId } })).toBeNull(); + const [pending] = await db.select().from(agentWakeupRequests).where(eq(agentWakeupRequests.id, queueId)); + expect(pending).toMatchObject({ requestedByActorType: "user", requestedByActorId: operatorId, + payload: { manualUserWake: true } }); + } finally { + finish(); + } + await drainHeartbeatRunsToQuiescence(db, heartbeat); + const successors = (await db.select().from(heartbeatRuns)).filter(run => run.id !== first!.id); + expect(successors.length).toBeGreaterThan(0); + expect(successors.every(run => run.responsibleUserId === operatorId && run.status === "succeeded")).toBe(true); + expect((await db.select().from(issueComments).where(eq(issueComments.id, commentId)))[0].authorUserId).toBe(ownerUserId); + }); + + it("starts an unscoped manual wake with its own user instead of joining another user's run", async () => { + const { companyId, agentId, ownerUserId } = await seedCompany(); + const operatorId = `operator-${randomUUID()}`; + await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: operatorId, + membershipRole: "operator", status: "active" }); + let finish!: () => void; + const blocked = new Promise(resolve => { finish = resolve; }); + const execute = mockAdapterExecute.getMockImplementation()!; + mockAdapterExecute.mockImplementationOnce(async () => { await blocked; return execute(); }); + const first = await heartbeat.wakeup(agentId, { manualUserWake: true, source: "on_demand", triggerDetail: "manual", + requestedByActorType: "user", requestedByActorId: ownerUserId }); + let second: Awaited>; + try { + await vi.waitFor(() => expect(mockAdapterExecute).toHaveBeenCalled(), { timeout: 5_000 }); + second = await heartbeat.wakeup(agentId, { manualUserWake: true, source: "on_demand", triggerDetail: "manual", + requestedByActorType: "user", requestedByActorId: operatorId }); + expect(second?.id).not.toBe(first!.id); + expect(second?.responsibleUserId).toBe(operatorId); + } finally { + finish(); + } + await drainHeartbeatRunsToQuiescence(db, heartbeat); + expect(await waitForRun(db, second!.id)).toMatchObject({ status: "succeeded", responsibleUserId: operatorId }); + expect(await waitForRun(db, first!.id)).toMatchObject({ status: "succeeded", responsibleUserId: ownerUserId }); + }); + + it("denies a manual wake of another user's private conversation", async () => { + const { companyId, agentId, ownerUserId } = await seedCompany(); + const issueId = randomUUID(); + await db.insert(issues).values({ id: issueId, companyId, title: "Private conversation", status: "todo", + assigneeAgentId: agentId, responsibleUserId: ownerUserId, conversationAgentId: agentId, conversationUserId: ownerUserId, conversationState: "active" }); + await expect(heartbeat.wakeup(agentId, { manualUserWake: true, source: "on_demand", triggerDetail: "manual", + payload: { issueId }, requestedByActorType: "user", requestedByActorId: "another-user" })).rejects.toThrow("conversation owner"); + expect(mockAdapterExecute).not.toHaveBeenCalled(); + expect(await db.select().from(heartbeatRuns)).toHaveLength(0); + }); + + it("does not accept a caller-supplied manual-wake authority marker", async () => { + const { companyId, agentId, ownerUserId } = await seedCompany(); + const run = await heartbeat.wakeup(agentId, { source: "on_demand", triggerDetail: "manual", + requestedByActorType: "agent", requestedByActorId: agentId, payload: { manualUserWake: true }, + contextSnapshot: { responsibleUserId: ownerUserId } }); + expect((await waitForRun(db, run!.id))?.status).toBe("succeeded"); + const [wake] = await db.select().from(agentWakeupRequests).where(eq(agentWakeupRequests.id, run!.wakeupRequestId!)); + expect(wake.payload?.manualUserWake).toBeUndefined(); + }); + it("uses the issue responsible user for automated dependency wakes without a message context", async () => { const { companyId, agentId } = await seedCompany(); const issueResponsibleUserId = `issue-owner-${randomUUID()}`; diff --git a/server/src/__tests__/heartbeat-stale-queue-invalidation.test.ts b/server/src/__tests__/heartbeat-stale-queue-invalidation.test.ts index 3fdf0fbb90..fc71329a30 100644 --- a/server/src/__tests__/heartbeat-stale-queue-invalidation.test.ts +++ b/server/src/__tests__/heartbeat-stale-queue-invalidation.test.ts @@ -1638,6 +1638,11 @@ describeEmbeddedPostgres("heartbeat stale queued-run invalidation", () => { originCommentIds: [wakeCommentId], interactionResolvedAt: new Date().toISOString(), mutation: "interaction", source: `${interactionKind}.resolved`, forceFreshSession: true } }); await heartbeat.resumeQueuedRuns(); + expect(await waitForCondition(async () => (await db.select({ status: heartbeatRuns.status }).from(heartbeatRuns).where(eq(heartbeatRuns.id, runId)))[0]?.status === "succeeded")).toBe(true); + // Terminal status precedes completion bookkeeping. Drain those writes before + // afterEach truncates the fixture, otherwise PostgreSQL can deadlock. + await heartbeat.waitForRunExecutionDrain(runId); + expect(countExecuteCallsForRun(runId)).toBe(1); await waitForCondition(async () => claimedIssue !== null); expect(claimedIssue).toEqual({ status: "in_progress", executionRunId: runId }); }); diff --git a/server/src/__tests__/issue-queued-comments-routes.test.ts b/server/src/__tests__/issue-queued-comments-routes.test.ts index 6a4f23a638..4b3bae7816 100644 --- a/server/src/__tests__/issue-queued-comments-routes.test.ts +++ b/server/src/__tests__/issue-queued-comments-routes.test.ts @@ -14,13 +14,14 @@ import { createDb, heartbeatRuns, issueComments, + issueRecoveryActions, issues, runIdentityContexts, } from "@paperclipai/db"; import { errorHandler } from "../middleware/index.js"; import { issueRoutes } from "../routes/issues.js"; import { heartbeatService } from "../services/heartbeat.js"; -import { reconcileSteeredIdentity } from "../services/run-identity.js"; +import { initializeRunIdentity, reconcileSteeredIdentity } from "../services/run-identity.js"; import { getEmbeddedPostgresTestSupport, startEmbeddedPostgresTestDatabase, @@ -195,6 +196,145 @@ describeEmbeddedPostgres("issue queued-comment routes", () => { }, ); + it("does not accept interruption authority from an agent wake payload", async () => { + const seeded = await seedQueue(); + await db.update(agents).set({ adapterType: "claude_local", + runtimeConfig: { heartbeat: { maxConcurrentRuns: 1 } }, + }).where(eq(agents.id, seeded.agentId)); + await db.update(heartbeatRuns).set({ runtimeMode: "legacy" }).where(eq(heartbeatRuns.id, seeded.runId)); + await heartbeatService(db).wakeup(seeded.agentId, { + source: "on_demand", reason: "issue_commented", + requestedByActorType: "agent", requestedByActorId: seeded.agentId, + payload: { issueId: seeded.issueId, commentId: seeded.commentIds[1], + queuedCommentInterrupt: { actorId: "other-operator", requestedAt: new Date().toISOString() } }, + contextSnapshot: { issueId: seeded.issueId, wakeCommentId: seeded.commentIds[1] }, + }); + const wakes = await db.select().from(agentWakeupRequests).where(eq(agentWakeupRequests.companyId, seeded.companyId)); + expect(wakes.length).toBeGreaterThan(0); + expect(wakes.every(wake => !wake.payload?.queuedCommentInterrupt)).toBe(true); + const runs = await db.select().from(heartbeatRuns).where(eq(heartbeatRuns.companyId, seeded.companyId)); + expect(runs.find(run => run.id === seeded.runId)?.status).toBe("running"); + expect(runs.every(run => !run.contextSnapshot?.explicitUserContinuation)).toBe(true); + }); + + it("denies a viewer's interrupt before persisting intent or cancelling a run", async () => { + const seeded = await seedQueue(); + await db.update(companyMemberships).set({ membershipRole: "viewer" }) + .where(eq(companyMemberships.principalId, "other-operator")); + const client = app(seeded.companyId, "other-operator"); + const queue = await request(client).get(`/api/issues/${seeded.issueId}/queued-comments`).expect(200); + await request(client).post(`/api/issues/${seeded.issueId}/queued-comments/interrupt`).send({ + queueId: seeded.wakeId, revision: queue.body.revision, targetRunId: seeded.runId, + }).expect(403); + const [wake] = await db.select().from(agentWakeupRequests).where(eq(agentWakeupRequests.id, seeded.wakeId)); + expect(wake.payload?.queuedCommentInterrupt).toBeUndefined(); + expect((await db.select().from(heartbeatRuns).where(eq(heartbeatRuns.id, seeded.runId)))[0].status).toBe("running"); + }); + + it.each([null, "stopped-target", "system-receipt"])("sends a stopped legacy queue once with target %s", async (target) => { + const seeded = await seedQueue(); + if (target === "system-receipt") await db.update(agentWakeupRequests).set({ + requestedByActorType: "system", requestedByActorId: "heartbeat", + }).where(eq(agentWakeupRequests.id, seeded.wakeId)); + await db.update(agents).set({ adapterType: "claude_local", + runtimeConfig: { heartbeat: { maxConcurrentRuns: 1 } }, + }).where(eq(agents.id, seeded.agentId)); + await db.update(heartbeatRuns).set({ runtimeMode: "legacy", status: "succeeded", + finishedAt: new Date("2026-08-22T15:03:00.000Z"), + }).where(eq(heartbeatRuns.id, seeded.runId)); + await db.update(issues).set({ executionRunId: null }).where(eq(issues.id, seeded.issueId)); + // Occupy this agent on a different task so the actual successor remains + // queued and the test never launches a provider. + await db.insert(heartbeatRuns).values({ companyId: seeded.companyId, agentId: seeded.agentId, + status: "running", contextSnapshot: { issueId: randomUUID() }, + }); + const client = app(seeded.companyId, "other-operator"); + const queue = await request(client).get(`/api/issues/${seeded.issueId}/queued-comments`).expect(200); + expect(queue.body.targetRunId).toBeNull(); + const body = { queueId: seeded.wakeId, revision: queue.body.revision, + targetRunId: target === "stopped-target" ? seeded.runId : null }; + await request(client).post(`/api/issues/${seeded.issueId}/queued-comments/interrupt`).send(body).expect(200); + const [wake] = await db.select().from(agentWakeupRequests).where(eq(agentWakeupRequests.id, seeded.wakeId)); + expect(wake.status).toBe("coalesced"); + const [successor] = await db.select().from(heartbeatRuns).where(eq(heartbeatRuns.id, wake.runId!)); + expect(successor.status).toBe("queued"); + expect(successor.responsibleUserId).toBe("other-operator"); + const identity = await initializeRunIdentity(db, { + companyId: seeded.companyId, issueId: seeded.issueId, + runId: successor.id, messageIds: seeded.commentIds, responsibleUserId: "queue-owner", cause: "dispatch", + }); + expect(identity.responsibleUserId).toBe("other-operator"); + expect(identity.cause).toBe("queued_comment_interrupt"); + expect(successor.contextSnapshot?.wakeCommentIds).toEqual(seeded.commentIds); + await heartbeatService(db).resumeQueuedCommentInterrupt(seeded.companyId, seeded.wakeId); + expect(await db.select().from(heartbeatRuns).where(eq(heartbeatRuns.companyId, seeded.companyId))).toHaveLength(3); + await request(client).post(`/api/issues/${seeded.issueId}/queued-comments/interrupt`).send(body).expect(409); + }); + + it.each(["user", "system"])("keeps stopped-run interruption intent on a %s receipt across restart until the process stops, then delivers once", async (actorType) => { + const seeded = await seedQueue(); + await db.update(agentWakeupRequests).set({ requestedByActorType: actorType }) + .where(eq(agentWakeupRequests.id, seeded.wakeId)); + await db.update(agents).set({ adapterType: "claude_local", + runtimeConfig: { heartbeat: { maxConcurrentRuns: 1 } }, + }).where(eq(agents.id, seeded.agentId)); + await db.update(heartbeatRuns).set({ runtimeMode: "legacy", status: "failed", + processPid: process.pid, errorCode: "process_lost", + finishedAt: new Date("2026-08-22T15:03:00.000Z"), + }).where(eq(heartbeatRuns.id, seeded.runId)); + await db.update(issues).set({ executionRunId: null }).where(eq(issues.id, seeded.issueId)); + await db.insert(issueRecoveryActions).values({ companyId: seeded.companyId, sourceIssueId: seeded.issueId, + kind: "active_run_watchdog", cause: "legacy_execution_requires_reconciliation", fingerprint: seeded.runId, + status: "resolved", outcome: "blocked", nextAction: "Automatic recovery stopped.", + evidence: { runId: seeded.runId, automaticRecovery: { replay: "blocked", actionOutcome: "unknown" } }, + }); + await db.insert(heartbeatRuns).values({ companyId: seeded.companyId, agentId: seeded.agentId, + status: "running", contextSnapshot: { issueId: randomUUID() }, + }); + const client = app(seeded.companyId, "other-operator"); + const queue = await request(client).get(`/api/issues/${seeded.issueId}/queued-comments`).expect(200); + await request(client).post(`/api/issues/${seeded.issueId}/queued-comments/interrupt`).send({ + queueId: seeded.wakeId, revision: queue.body.revision, targetRunId: null, + }).expect(200); + const [waiting] = await db.select().from(agentWakeupRequests).where(eq(agentWakeupRequests.id, seeded.wakeId)); + expect(waiting.status).toBe("deferred_issue_execution"); + expect(waiting.payload?.queuedCommentInterrupt).toMatchObject({ actorId: "other-operator" }); + expect(waiting.payload?.executionWait).toMatchObject({ reason: "process_running" }); + expect(await db.select().from(heartbeatRuns).where(eq(heartbeatRuns.companyId, seeded.companyId))).toHaveLength(2); + await db.update(heartbeatRuns).set({ processPid: 999999999 }).where(eq(heartbeatRuns.id, seeded.runId)); + await db.update(agentWakeupRequests).set({ updatedAt: new Date(0) }).where(eq(agentWakeupRequests.id, seeded.wakeId)); + // New service instances have no memory of the HTTP request. Concurrent + // periodic workers must consume its durable receipt exactly once. + await Promise.all([heartbeatService(db).resumeQueuedRuns(), heartbeatService(db).resumeQueuedRuns()]); + const [delivered] = await db.select().from(agentWakeupRequests).where(eq(agentWakeupRequests.id, seeded.wakeId)); + expect(delivered.status).toBe("coalesced"); + const [successor] = await db.select().from(heartbeatRuns).where(eq(heartbeatRuns.id, delivered.runId!)); + expect(successor.contextSnapshot).toMatchObject({ wakeCommentIds: seeded.commentIds, + previousRunId: seeded.runId, forceFreshSession: true }); + expect(await db.select().from(heartbeatRuns).where(eq(heartbeatRuns.companyId, seeded.companyId))).toHaveLength(3); + }); + + it("recovers a message deferred after legacy finalization released the task lock", async () => { + const seeded = await seedQueue(); + await db.update(agents).set({ adapterType: "claude_local", + runtimeConfig: { heartbeat: { maxConcurrentRuns: 1 } }, + }).where(eq(agents.id, seeded.agentId)); + await db.update(heartbeatRuns).set({ runtimeMode: "legacy", status: "succeeded", + finishedAt: new Date("2026-08-22T15:03:00.000Z"), + }).where(eq(heartbeatRuns.id, seeded.runId)); + await db.update(issues).set({ executionRunId: null }).where(eq(issues.id, seeded.issueId)); + await db.insert(heartbeatRuns).values({ companyId: seeded.companyId, agentId: seeded.agentId, + status: "running", contextSnapshot: { issueId: randomUUID() }, + }); + await heartbeatService(db).resumeQueuedRuns(); + const [wake] = await db.select().from(agentWakeupRequests).where(eq(agentWakeupRequests.id, seeded.wakeId)); + expect(wake.status).toBe("queued"); + const [successor] = await db.select().from(heartbeatRuns).where(eq(heartbeatRuns.id, wake.runId!)); + expect(successor.contextSnapshot?.wakeCommentIds).toEqual(seeded.commentIds); + await heartbeatService(db).resumeQueuedRuns(); + expect(await db.select().from(heartbeatRuns).where(eq(heartbeatRuns.companyId, seeded.companyId))).toHaveLength(3); + }); + async function promoteQueue(seeded: Awaited>) { const queueRunId = randomUUID(); const wake = await db diff --git a/server/src/__tests__/issues-service.test.ts b/server/src/__tests__/issues-service.test.ts index 3b91c8856c..9309731a6d 100644 --- a/server/src/__tests__/issues-service.test.ts +++ b/server/src/__tests__/issues-service.test.ts @@ -1466,7 +1466,7 @@ describeEmbeddedPostgres("issueService.list participantAgentId", () => { expect(result.map((issue) => issue.id)).toEqual([recentMediumIssueId]); }); - it("ranks comment matches ahead of description-only matches", async () => { + it("ranks direct description matches ahead of comment-only matches", async () => { const companyId = randomUUID(); const commentMatchId = randomUUID(); const descriptionMatchId = randomUUID(); @@ -1508,7 +1508,7 @@ describeEmbeddedPostgres("issueService.list participantAgentId", () => { includeRoutineExecutions: true, }); - expect(result.map((issue) => issue.id)).toEqual([commentMatchId, descriptionMatchId]); + expect(result.map((issue) => issue.id)).toEqual([descriptionMatchId, commentMatchId]); }); it("filters issue lists to the full descendant tree for a root issue", async () => { diff --git a/server/src/__tests__/local-ai-credential-file.test.ts b/server/src/__tests__/local-ai-credential-file.test.ts new file mode 100644 index 0000000000..c2db45bf86 --- /dev/null +++ b/server/src/__tests__/local-ai-credential-file.test.ts @@ -0,0 +1,29 @@ +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { chmod, mkdtemp, realpath, rm, symlink, writeFile } from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { readLocalAiCredentialFile } from "../services/local-ai-credential-file.js"; +let home: string; +beforeEach(async () => { home = await realpath(await mkdtemp(path.join(os.tmpdir(), "ai-auth-read-"))); }); +afterEach(async () => { await rm(home, { recursive: true, force: true }); }); +describe("isolated credential file safety", () => { + it("reads only bounded private regular files", async () => { + const filename = path.join(home, "credentials.json"); + await writeFile(filename, "fixture", { mode: 0o600 }); + await expect(readLocalAiCredentialFile(filename)).resolves.toBe("fixture"); + await chmod(filename, 0o644); + await expect(readLocalAiCredentialFile(filename)).rejects.toThrow(); + await chmod(filename, 0o600); + await writeFile(filename, Buffer.alloc(64 * 1024 + 1)); + await expect(readLocalAiCredentialFile(filename)).rejects.toThrow(); + await expect(readLocalAiCredentialFile(home)).rejects.toThrow(); + }); + it("rejects file and ancestor symlinks", async () => { + const filename = path.join(home, "credentials.json"); + await writeFile(filename, "fixture", { mode: 0o600 }); + await symlink(filename, path.join(home, "linked.json")); + await expect(readLocalAiCredentialFile(path.join(home, "linked.json"))).rejects.toThrow(); + await symlink(home, path.join(home, "linked-home")); + await expect(readLocalAiCredentialFile(path.join(home, "linked-home", "credentials.json"))).rejects.toThrow(); + }); +}); diff --git a/server/src/__tests__/local-ai-credentials.test.ts b/server/src/__tests__/local-ai-credentials.test.ts new file mode 100644 index 0000000000..642ac5e7ae --- /dev/null +++ b/server/src/__tests__/local-ai-credentials.test.ts @@ -0,0 +1,68 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import { readVerifiedLocalAiCredential } from "../services/local-ai-credentials.js"; +const mocks = vi.hoisted(() => ({ claude: vi.fn(), claudeQuota: vi.fn(), codex: vi.fn(), codexQuota: vi.fn(), readFile: vi.fn(), credentialFile: vi.fn() })); +vi.mock("@paperclipai/adapter-claude-local/server", () => ({ readClaudeToken: mocks.claude, fetchClaudeQuota: mocks.claudeQuota })); +vi.mock("@paperclipai/adapter-codex-local/server", () => ({ readCodexAuthInfo: mocks.codex, fetchCodexQuota: mocks.codexQuota })); +vi.mock("../services/local-ai-credential-file.js", () => ({ readLocalAiCredentialFile: mocks.credentialFile })); +vi.mock("node:fs/promises", () => ({ default: { readFile: mocks.readFile } })); +afterEach(() => { vi.resetAllMocks(); vi.unstubAllGlobals(); }); +describe("explicit local subscription import", () => { + it("verifies Claude only from the selected isolated home, never the host account", async () => { + mocks.credentialFile.mockResolvedValue(JSON.stringify({ claudeAiOauth: { accessToken: "isolated-claude" } })); + await expect(readVerifiedLocalAiCredential("anthropic", "/isolated/claude")).resolves.toBe("isolated-claude"); + expect(mocks.credentialFile).toHaveBeenCalledWith("/isolated/claude/.credentials.json"); + expect(mocks.claudeQuota).toHaveBeenCalledWith("isolated-claude"); + expect(mocks.claude).not.toHaveBeenCalled(); + }); + it("does not fall back to ambient Claude auth when an isolated login is absent or invalid", async () => { + mocks.claude.mockResolvedValue("server-operator-token"); + mocks.credentialFile.mockRejectedValue(new Error("No file")); + await expect(readVerifiedLocalAiCredential("anthropic", "/isolated/claude")).rejects.toThrow("sign-in command shown"); + mocks.credentialFile.mockResolvedValue("malformed"); + await expect(readVerifiedLocalAiCredential("anthropic", "/isolated/claude")).rejects.toThrow("sign-in command shown"); + expect(mocks.claude).not.toHaveBeenCalled(); + expect(mocks.claudeQuota).not.toHaveBeenCalled(); + }); + it("tries the alternate Claude filename after malformed JSON", async () => { + mocks.credentialFile.mockResolvedValueOnce("malformed").mockResolvedValueOnce(JSON.stringify({ claudeAiOauth: { accessToken: "alternate-token" } })); + await expect(readVerifiedLocalAiCredential("anthropic", "/isolated/claude")).resolves.toBe("alternate-token"); + expect(mocks.credentialFile).toHaveBeenLastCalledWith("/isolated/claude/credentials.json"); + expect(mocks.claude).not.toHaveBeenCalled(); + }); + it("verifies Claude's local credential, including explicit Keychain access", async () => { + mocks.claude.mockResolvedValue("fixture-claude"); + await expect(readVerifiedLocalAiCredential("anthropic")).resolves.toBe("fixture-claude"); + expect(mocks.claude).toHaveBeenCalledWith({ allowKeychain: true }); + expect(mocks.claudeQuota).toHaveBeenCalledWith("fixture-claude"); + }); + it("reads Codex refresh credentials only from the isolated login home", async () => { + mocks.codex.mockResolvedValue({ accessToken: "access", refreshToken: "refresh", idToken: "identity", accountId: "account", lastRefresh: "date" }); + const result = JSON.parse(await readVerifiedLocalAiCredential("openai", "/isolated/login")); + expect(result.tokens).toEqual({ access_token: "access", refresh_token: "refresh", id_token: "identity", account_id: "account" }); + expect(mocks.codexQuota).toHaveBeenCalledWith("access", "account"); + expect(mocks.codex).toHaveBeenCalledWith("/isolated/login"); + }); + it("verifies a Grok subscription against a fixed endpoint before saving", async () => { + const credential = JSON.stringify({ "https://issuer.x.ai::11111111-1111-4111-8111-111111111111": { key: "fixture-key", refresh_token: "fixture-refresh" } }); + mocks.readFile.mockResolvedValue(credential); + const fetch = vi.fn().mockResolvedValue(new Response("{}")); vi.stubGlobal("fetch", fetch); + await expect(readVerifiedLocalAiCredential("xai", "/isolated/grok")).resolves.toBe(credential); + expect(mocks.readFile).toHaveBeenCalledWith("/isolated/grok/auth.json", "utf8"); + expect(fetch).toHaveBeenCalledWith("https://api.x.ai/v1/models", expect.objectContaining({ redirect: "error" })); + }); + it("rejects missing and invalid logins with actionable, redacted errors", async () => { + mocks.claude.mockResolvedValue(null); + await expect(readVerifiedLocalAiCredential("anthropic")).rejects.toThrow("claude auth login"); + mocks.claude.mockResolvedValue("fixture-secret"); + mocks.claudeQuota.mockRejectedValue(new Error("credential fixture-secret rejected")); + await expect(readVerifiedLocalAiCredential("anthropic")).rejects.toThrow(/^Could not verify the local subscription\. Run claude auth login in a terminal on the machine running Paperclip, then try Connect again\.$/); + mocks.codex.mockResolvedValue({ accessToken: "incomplete" }); + await expect(readVerifiedLocalAiCredential("openai", "/isolated/login")).rejects.toThrow("sign-in command shown"); + expect(mocks.codexQuota).not.toHaveBeenCalled(); + }); + it.each(["openai", "xai"] as const)("never clones the ambient rotating %s login", async (provider) => { + await expect(readVerifiedLocalAiCredential(provider)).rejects.toThrow("separate local sign-in"); + expect(mocks.codex).not.toHaveBeenCalled(); + expect(mocks.readFile).not.toHaveBeenCalled(); + }); +}); diff --git a/server/src/__tests__/local-ai-login-policy.test.ts b/server/src/__tests__/local-ai-login-policy.test.ts new file mode 100644 index 0000000000..e7630a6151 --- /dev/null +++ b/server/src/__tests__/local-ai-login-policy.test.ts @@ -0,0 +1,10 @@ +import { describe, expect, it } from "vitest"; +import { supportsLocalAiLogin } from "../services/local-ai-login-policy.js"; +describe("server-host subscription login policy", () => { + it("permits private self-hosted instances and explicit trusted hosts", () => { + expect(supportsLocalAiLogin({ deploymentMode: "local_trusted", deploymentExposure: "private" })).toBe(true); + expect(supportsLocalAiLogin({ deploymentMode: "authenticated", deploymentExposure: "private" })).toBe(true); + expect(supportsLocalAiLogin({ deploymentMode: "authenticated", deploymentExposure: "public", trustedLocalStdioRuntimeHost: "trusted-host" })).toBe(true); + expect(supportsLocalAiLogin({ deploymentMode: "authenticated", deploymentExposure: "public", trustedLocalStdioRuntimeHost: "" })).toBe(false); + }); +}); diff --git a/server/src/__tests__/openapi-routes.test.ts b/server/src/__tests__/openapi-routes.test.ts index 4abc30cff4..02dd53a522 100644 --- a/server/src/__tests__/openapi-routes.test.ts +++ b/server/src/__tests__/openapi-routes.test.ts @@ -20,6 +20,7 @@ const apiPrefixes: Record = { "activity.ts": "/api", "adapters.ts": "/api", "agents.ts": "/api", + "ai-connections.ts": "/api", "attention.ts": "/api", "approvals.ts": "/api", "assets.ts": "/api", @@ -391,6 +392,7 @@ describe("openapi routes", () => { ["post", "/api/chat-endpoints/{endpointId}/setup"], ["post", "/api/chat-endpoints/{endpointId}/setup-secret"], ["post", "/api/chat-endpoints/{endpointId}/test"], + ["post", "/api/chat-endpoints/{endpointId}/photon/inspect"], ["get", "/api/chat-endpoints/{endpointId}/resources"], ["put", "/api/chat-endpoints/{endpointId}/resources"], ["get", "/api/chat-endpoints/{endpointId}/principals"], @@ -455,7 +457,7 @@ describe("openapi routes", () => { properties: { provider: { type: "string", - enum: ["slack", "github", "discord", "microsoft-teams", "telegram"], + enum: ["slack", "github", "discord", "microsoft-teams", "telegram", "imessage-photon"], }, assignedAgentId: { type: "string", format: "uuid" }, }, @@ -515,6 +517,21 @@ describe("openapi routes", () => { ); expect(setup.responses["409"]).toBeDefined(); expect(setup.responses["422"]).toBeDefined(); + expect(setup.responses["502"]).toBeDefined(); + expect(setup.responses["503"]).toBeDefined(); + + const photon = spec.paths["/api/chat-endpoints/{endpointId}/photon/inspect"].post; + expect(photon.requestBody.content["application/json"].schema.required).toEqual([ + "projectId", "projectSecret", + ]); + const photonResponse = photon.responses["200"].content["application/json"].schema; + expect(photonResponse.properties.allocation.enum).toEqual(["dedicated", "shared"]); + expect(photonResponse.properties.lines.items.additionalProperties).toBe(false); + expect(JSON.stringify(photonResponse)).not.toMatch(/projectSecret|token/); + expect(photon.responses["422"]).toBeDefined(); + expect(photon.responses["429"]).toBeDefined(); + expect(photon.responses["502"]).toBeDefined(); + expect(photon.responses["503"]).toBeDefined(); const setupSecret = spec.paths["/api/chat-endpoints/{endpointId}/setup-secret"].post; diff --git a/server/src/__tests__/opencode-local-adapter-environment.test.ts b/server/src/__tests__/opencode-local-adapter-environment.test.ts index 956bb975f1..2a0e44a274 100644 --- a/server/src/__tests__/opencode-local-adapter-environment.test.ts +++ b/server/src/__tests__/opencode-local-adapter-environment.test.ts @@ -20,6 +20,7 @@ describe("opencode_local environment diagnostics", () => { config: { command: process.execPath, cwd, + env: { XDG_CONFIG_HOME: path.join(cwd, "config") }, }, }); @@ -45,6 +46,7 @@ describe("opencode_local environment diagnostics", () => { cwd, env: { OPENAI_API_KEY: "", + XDG_CONFIG_HOME: path.join(cwd, "config"), }, }, }); @@ -84,6 +86,7 @@ describe("opencode_local environment diagnostics", () => { config: { command: fakeOpencode, cwd, + env: { XDG_CONFIG_HOME: path.join(cwd, "config") }, }, }); diff --git a/server/src/__tests__/permissions-upgrade-boundary-routes.test.ts b/server/src/__tests__/permissions-upgrade-boundary-routes.test.ts index d0e7222891..89c00485e8 100644 --- a/server/src/__tests__/permissions-upgrade-boundary-routes.test.ts +++ b/server/src/__tests__/permissions-upgrade-boundary-routes.test.ts @@ -34,6 +34,9 @@ vi.mock("../services/issue-assignment-wakeup.js", () => ({ queueIssueAssignmentWakeup: vi.fn(), })); +import { activityRoutes } from "../routes/activity.js"; +import { issueRoutes } from "../routes/issues.js"; + const embeddedPostgresSupport = await getEmbeddedPostgresTestSupport(); const describeEmbeddedPostgres = embeddedPostgresSupport.supported ? describe : describe.skip; @@ -52,10 +55,6 @@ function agentActor(companyId: string, agentId: string): Express.Request["actor" async function createApp(db: Db, actor: Express.Request["actor"]) { process.env.PAPERCLIP_LOG_DIR = "/tmp/paperclip-test-home/logs"; process.env.PAPERCLIP_IN_WORKTREE = "false"; - const [{ activityRoutes }, { issueRoutes }] = await Promise.all([ - import("../routes/activity.js"), - import("../routes/issues.js"), - ]); const app = express(); app.use(express.json()); app.use((req, _res, next) => { diff --git a/server/src/__tests__/photon/channel.integration.test.ts b/server/src/__tests__/photon/channel.integration.test.ts new file mode 100644 index 0000000000..257992f8ee --- /dev/null +++ b/server/src/__tests__/photon/channel.integration.test.ts @@ -0,0 +1,1278 @@ +import { Readable } from "node:stream"; +import type { StorageService } from "../../storage/types.js"; +import { createHash, randomUUID } from "node:crypto"; +import { mkdtemp, rm, readFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import { + beforeAll, + afterAll, + beforeEach, + afterEach, + describe, + it, + expect, + vi, +} from "vitest"; +import { + TypedEventStream, + type LiveEvent, + type GrpcAdvancedIMessage, +} from "@photon-ai/advanced-imessage"; +import { and, eq, sql } from "drizzle-orm"; +import { + createDb, + companies, + agents, + authUsers, + companyMemberships, + principalPermissionGrants, + chatEndpointLeases, + chatSdkState, + chatEndpoints, + chatConversations, + chatDeliveries, + chatMessageLinks, + chatActions, + chatPublications, + heartbeatRuns, + agentWakeupRequests, + issueComments, + issues, + activityLog, + issueQuestionResponseDeliveries, + chatIdentityLinks, + chatEndpointResources, + issueAttachments, + assets, +} from "@paperclipai/db"; +import { startEmbeddedPostgresTestDatabase } from "../helpers/embedded-postgres.js"; +import { + chatChannelService, + type ChatChannelService, +} from "../../services/chat-channels.js"; +import { ChatSdkRuntime } from "../../services/chat-sdk-runtime.js"; +import { issueService } from "../../services/issues.js"; +import { subscribeCompanyLiveEvents } from "../../services/live-events.js"; +import { issueThreadInteractionService } from "../../services/issue-thread-interactions.js"; +import { resolveExternalChatQuestionResponse } from "../../services/native-runtime/external-chat-question-response.js"; +import { resolveChatRunPresentationAuthorizationReason } from "../../services/chat-run-publications.js"; +import { PhotonChatAdapter } from "../../services/photon/adapter.js"; +import { PhotonCloudClient, PhotonError } from "../../services/photon/cloud.js"; +import { photonFixture, photonEvent, photonChat, stream } from "./fixture.js"; + +function idleStream(): TypedEventStream { + let close!: () => void; + const done = new Promise((resolve) => { + close = resolve; + }); + return new TypedEventStream( + (async function* () { + await done; + })(), + async () => close(), + ); +} +describe.sequential("iMessage Photon channel control plane", () => { + let database: Awaited>; + let db: ReturnType; + let secrets: string; + const oldKey = process.env.PAPERCLIP_SECRETS_MASTER_KEY_FILE; + const services: ChatChannelService[] = []; + const companyIds: string[] = []; + beforeAll(async () => { + database = await startEmbeddedPostgresTestDatabase("paperclip-photon-"); + db = createDb(database.connectionString); + secrets = await mkdtemp(path.join(tmpdir(), "photon-secrets-")); + process.env.PAPERCLIP_SECRETS_MASTER_KEY_FILE = path.join( + secrets, + "master.key", + ); + }, 30_000); + afterEach(async () => { + await Promise.all(services.splice(0).map((service) => service.shutdown())); + for (const id of companyIds.splice(0)) + await db + .update(chatEndpoints) + .set({ status: "archived" }) + .where(eq(chatEndpoints.companyId, id)); + vi.restoreAllMocks(); + }); + afterAll(async () => { + await database?.cleanup(); + if (secrets) await rm(secrets, { recursive: true, force: true }); + if (oldKey === undefined) + delete process.env.PAPERCLIP_SECRETS_MASTER_KEY_FILE; + else process.env.PAPERCLIP_SECRETS_MASTER_KEY_FILE = oldKey; + }); + async function setup(shared = false) { + const companyId = randomUUID(), + agentId = randomUUID(), + userId = randomUUID(); + companyIds.push(companyId); + await db.insert(companies).values({ + id: companyId, + name: "Photon test", + issuePrefix: `P${companyId.replaceAll("-", "").slice(0, 7).toUpperCase()}`, + requireBoardApprovalForNewAgents: false, + }); + await db.insert(agents).values({ + id: agentId, + companyId, + name: "Photon Agent", + role: "engineer", + status: "idle", + adapterType: "paperclip_runner", + adapterConfig: {}, + runtimeConfig: {}, + permissions: {}, + }); + await db.insert(authUsers).values({ + id: userId, + name: "Operator", + email: `${userId}@example.com`, + emailVerified: true, + createdAt: new Date(), + updatedAt: new Date(), + }); + await db.insert(companyMemberships).values({ + companyId, + principalType: "user", + principalId: userId, + status: "active", + membershipRole: "operator", + }); + await db.insert(principalPermissionGrants).values({ + companyId, + principalType: "user", + principalId: userId, + permissionKey: "tools:manage_connections", + grantedByUserId: userId, + }); + const f = photonFixture(); + const allocation = shared ? { + inspection: { projectId: "project", projectName: "Test shared", allocation: "shared" as const, eligible: true, lines: [] }, + tokens: new Map(), sharedToken: "shared-project-token", expiresIn: 300, + } : await f.cloud.allocation("project", "secret"); + vi.spyOn(PhotonCloudClient.prototype, "allocation").mockResolvedValue( + allocation, + ); + for (const resource of [ + f.client.messages, + f.client.chats, + f.client.groups, + f.client.polls, + ]) + resource.subscribeEvents.mockImplementation(() => idleStream()); + vi.spyOn(PhotonChatAdapter.prototype, "initialize").mockImplementation( + async function (this: PhotonChatAdapter) { + await this.client.close(); + Object.defineProperty(this, "client", { + value: f.client as unknown as GrpcAdvancedIMessage, + }); + await this.authentication.token(); + }, + ); + vi.spyOn(PhotonChatAdapter.prototype, "recoveryStream").mockImplementation( + () => stream([{ type: "catchup.complete", headSequence: 0 }]), + ); + const chats = new Map([[f.chat.guid, f.chat]]); + f.client.chats.get.mockImplementation(async (guid?: string) => { + const chat = chats.get(guid ?? ""); + if (!chat) throw new Error("Missing chat"); + return chat; + }); + let runtime = new ChatSdkRuntime(); + let replacement = vi.spyOn(runtime, "replaceEndpoint"); + const wakeup = vi.fn(async (assignedAgentId, opts) => { + const request = opts.durableChatRequest; + if (request) + await db.transaction(async (tx) => { + await request.authorize(tx); + await tx + .insert(agentWakeupRequests) + .values({ + id: request.id, + companyId: request.companyId, + agentId: assignedAgentId, + source: opts.source, + triggerDetail: opts.triggerDetail, + reason: opts.reason, + payload: opts.payload, + requestedByActorType: opts.requestedByActorType, + requestedByActorId: opts.requestedByActorId, + idempotencyKey: request.idempotencyKey, + requestedAt: request.requestedAt, + status: "queued", + }) + .onConflictDoNothing(); + }); + return { accepted: true }; + }); + const objects = new Map(); + const storage: StorageService = { + provider: "local_disk", + putFile: async (input) => { + const objectKey = `${input.namespace}/${randomUUID()}`; + objects.set(objectKey, input.body); + return { + provider: "local_disk", + objectKey, + contentType: input.contentType, + byteSize: input.body.length, + sha256: createHash("sha256").update(input.body).digest("hex"), + originalFilename: input.originalFilename, + }; + }, + getObject: async (_company, key) => ({ + stream: Readable.from([objects.get(key)!]), + contentLength: objects.get(key)!.length, + }), + headObject: async (_company, key) => ({ exists: objects.has(key) }), + deleteObject: async (_company, key) => { + objects.delete(key); + }, + }; + const inboundMessages = new Map(); + f.client.messages.get.mockImplementation( + async (id) => + inboundMessages.get(id) ?? + [...f.receipts.values()].find((message) => message.guid === id), + ); + const makeService = () => { + const service = chatChannelService(db, { + runtime, + publicBaseUrl: "https://paperclip.example", + heartbeat: { wakeup }, + storage, + scheduleDeferredWork: () => {}, + }); + services.push(service); + return service; + }; + let service = makeService(); + const endpoint = await service.create( + companyId, + { provider: "imessage-photon", assignedAgentId: agentId }, + userId, + ); + await service.configure( + endpoint.id, + { + action: "configure", + photon: shared ? { allocation: "shared", projectId: "project" } : { projectId: "project", lineId: "line" }, + credentials: { projectSecret: "secret" }, + }, + userId, + ); + const callbacks = () => replacement.mock.calls.at(-1)![0].callbacks; + const deliver = async (event: LiveEvent) => { + if (event.type === "message.received") + inboundMessages.set(event.message.guid, event.message); + await callbacks().onPhotonEvent!(event); + await service.processPendingDeliveries(); + }; + const link = async (address = "+15555550101", person = userId) => { + const principal = (await service.listPrincipals(endpoint.id)).find( + (entry) => entry.externalLabel === address, + )!; + expect(principal).toBeDefined(); + const intent = await service.createLinkIntent( + endpoint.id, + principal.principalId, + 1800, + ); + await service.confirmIdentityLink( + new URL(intent.confirmationUrl).searchParams.get("token")!, + person, + ); + }; + const start = async () => { + await deliver(photonEvent(1)); + expect(await service.listConversations(endpoint.id)).toHaveLength(0); + await link(); + await deliver(photonEvent(2)); + const [conversation] = await service.listConversations(endpoint.id); + expect(conversation).toBeDefined(); + return conversation; + }; + const qualify = async () => { + const [conversation] = await service.listConversations(endpoint.id); + const [source] = await db + .select() + .from(chatMessageLinks) + .where( + and( + eq(chatMessageLinks.endpointId, endpoint.id), + eq(chatMessageLinks.direction, "inbound"), + ), + ) + .orderBy(sql`${chatMessageLinks.createdAt} desc`) + .limit(1); + const runId = randomUUID(); + await db.insert(heartbeatRuns).values({ + id: runId, + companyId, + agentId, + status: "succeeded", + contextSnapshot: { + issueId: conversation.issueId, + source: "chat:imessage-photon", + wakeCommentId: source.commentId, + wakeCommentIds: [source.commentId], + }, + }); + const authorizationReason = + await resolveChatRunPresentationAuthorizationReason(db, { + companyId, + issueId: conversation.issueId, + runId, + }); + expect(authorizationReason).toBe("allow_chat_run_presentation"); + await issueService(db).addComment( + conversation.issueId, + "Agent reply through Photon", + { agentId, runId }, + { authorType: "agent", authorizationReason }, + ); + await service.processPendingPublications(); + await service.test(endpoint.id); + expect((await service.get(endpoint.id)).status).toBe("active"); + }; + return { + f, + companyId, + agentId, + userId, + endpoint, + service, + callbacks, + deliver, + link, + start, + qualify, + chats, + wakeup, + restart: async () => { + await service.shutdown(); + runtime = new ChatSdkRuntime(); + replacement = vi.spyOn(runtime, "replaceEndpoint"); + service = makeService(); + await service.reconcileProviderRuntimes(); + return service; + }, + }; + } + it("supports shared project DMs while rejecting groups, duplicate ownership and allocation changes", async () => { + const t = await setup(true); + const snapshot = await t.service.get(t.endpoint.id); + expect(snapshot).toMatchObject({ photonAllocation: "shared", botExternalId: "photon-project:project", botUsername: null, allowGroupChats: false }); + await expect(t.service.update(t.endpoint.id, { allowGroupChats: true }, t.userId)).rejects.toThrow(/direct messages only/); + const group = photonChat("iMessage;+;shared-group", true); + t.chats.set(group.guid, group); + await t.deliver(photonEvent(10, group)); + expect(await t.service.listResources(t.endpoint.id)).toHaveLength(0); + expect(t.wakeup).not.toHaveBeenCalled(); + const conversation = await t.start(); + expect(conversation.externalThreadId).toContain("shared-"); + await t.qualify(); + expect(t.f.client.groups.subscribeEvents).not.toHaveBeenCalled(); + const duplicate = await t.service.create(t.companyId, { provider: "imessage-photon", assignedAgentId: t.agentId }, t.userId); + expect(await t.service.inspectPhoton(duplicate.id, {projectId: "project", projectSecret: "secret"})).toMatchObject({ allocation: "shared", eligible: false }); + await expect(t.service.configure(duplicate.id, { action: "configure", photon: { allocation: "shared", projectId: "project" }, credentials: { projectSecret: "secret" } }, t.userId)).rejects.toThrow(/already/); + const restarted = await t.restart(); + expect((await restarted.listConversations(t.endpoint.id))[0].id).toBe(conversation.id); + await expect(restarted.configure(t.endpoint.id, { action: "reconnect", photon: { projectId: "project", lineId: "line" }, credentials: { projectSecret: "secret" } }, t.userId)).rejects.toThrow(/allocation|identity|different/); + }); + it("distinguishes setup validation from provider outages without replacing credentials", async () => { + const t = await setup(); + const allocation = vi.spyOn(PhotonCloudClient.prototype, "allocation"); + for (const [code, status] of [ + ["credentials", 422], + ["line_unavailable", 422], + ["quota", 429], + ["network", 503], + ["invalid_response", 502], + ] as const) { + allocation.mockRejectedValueOnce(new PhotonError(code, `Safe Photon ${code} message`)); + await expect(t.service.inspectPhoton(t.endpoint.id, { + projectId: "project", projectSecret: "replacement", + })).rejects.toMatchObject({ status, details: { code: `photon_${code}` } }); + allocation.mockRejectedValueOnce(new PhotonError(code, `Safe Photon ${code} message`)); + await expect(t.service.configure(t.endpoint.id, { + action: "reconnect", credentials: { projectSecret: "replacement" }, + }, t.userId)).rejects.toMatchObject({ status, details: { code: `photon_${code}` } }); + } + await t.start(); + await t.qualify(); + }); + it("requires a fresh linked message and an agent reply before setup completes", async () => { + const t = await setup(); + await expect(t.service.test(t.endpoint.id)).rejects.toThrow("test message"); + await t.start(); + await expect(t.service.test(t.endpoint.id)).rejects.toThrow(); + expect(t.wakeup).toHaveBeenCalledTimes(1); + await t.qualify(); + expect( + t.f.client.messages.sendText.mock.calls.find((call) => + call[1].includes("Agent reply"), + )?.[2], + ).toMatchObject({ replyTo: "message-2" }); + const secretSafe = JSON.stringify(await t.service.get(t.endpoint.id)); + expect(secretSafe).not.toContain("private-line-token"); + expect(secretSafe).not.toContain('"projectSecret"'); + }, 30_000); + it("preserves DM identity across restart, ignores echoes, and reserves a paused number", async () => { + const t = await setup(); + const first = await t.start(); + await t.qualify(); + let service = await t.restart(); + await t.deliver(photonEvent(3)); + expect((await service.listConversations(t.endpoint.id))[0].issueId).toBe( + first.issueId, + ); + const echo = photonEvent(4); + await t.deliver({ ...echo, isFromMe: true } as LiveEvent); + expect(t.wakeup).toHaveBeenCalledTimes(2); + await service.configure(t.endpoint.id, { action: "pause" }, t.userId); + const duplicate = await service.create( + t.companyId, + { provider: "imessage-photon", assignedAgentId: t.agentId }, + t.userId, + ); + await expect( + service.configure( + duplicate.id, + { + action: "configure", + photon: { projectId: "project", lineId: "line" }, + credentials: { projectSecret: "secret" }, + }, + t.userId, + ), + ).rejects.toThrow(/already|another/); + await service.configure(t.endpoint.id, { action: "resume" }, t.userId); + expect((await service.get(t.endpoint.id)).status).toBe("active"); + await service.configure(t.endpoint.id, { action: "remove" }, t.userId); + expect((await service.get(t.endpoint.id)).status).toBe("archived"); + }, 30_000); + it("discovers groups disabled and admits only fresh messages after enablement", async () => { + const t = await setup(); + await t.start(); + await t.qualify(); + const group = photonChat("iMessage;+;group", true); + t.chats.set(group.guid, group); + await t.deliver(photonEvent(3, group, "Old disabled request")); + expect(await t.service.listConversations(t.endpoint.id)).toHaveLength(1); + const resources = await t.service.listResources(t.endpoint.id); + const resource = resources.find((row) => row.type === "group_chat")!; + expect(resource.enabled).toBe(false); + await t.service.replaceResources( + t.endpoint.id, + resources.map((row) => ({ + id: row.id, + enabled: row.id === resource.id || row.enabled, + })), + t.userId, + ); + await t.service.processPendingDeliveries(); + expect(await t.service.listConversations(t.endpoint.id)).toHaveLength(1); + await t.deliver(photonEvent(4, group, "Fresh group request")); + expect(await t.service.listConversations(t.endpoint.id)).toHaveLength(2); + const conversation = (await t.service.listConversations(t.endpoint.id)).find((row) => !row.isDirectMessage)!; + await issueService(db).update(conversation.issueId, { status: "done", actorUserId: t.userId }); + await t.deliver(photonEvent(5, group, "A group follow-up after completion")); + const conversations = await t.service.listConversations(t.endpoint.id); + expect(conversations).toHaveLength(2); + expect(conversations.find((row) => !row.isDirectMessage)).toMatchObject({ id: conversation.id, issueId: conversation.issueId, state: "active" }); + }, 30_000); + it.each([true, false])("resolves an authorized exact poll vote once, including setup (qualified=%s)", async (qualified) => { + const t = await setup(); + const conversation = await t.start(); + if (qualified) await t.qualify(); + const interaction = await issueThreadInteractionService(db).create( + { id: conversation.issueId, companyId: t.companyId }, + { + kind: "ask_user_questions", + continuationPolicy: "none", + payload: { + version: 1, + questions: [ + { + id: "q", + prompt: "Choose", + selectionMode: "single", + required: true, + allowOther: false, + options: [ + { id: "one", label: "Same" }, + { id: "two", label: "Same" }, + ], + }, + ], + }, + }, + { agentId: t.agentId }, + ); + await t.service.processPendingPublications(); + expect(t.f.polls.size).toBe(1); + const poll = [...t.f.polls.values()][0]; + const vote = { + type: "poll.changed", + sequence: 5, + chatGuid: t.f.chat.guid, + occurredAt: new Date(), + isFromMe: false, + actor: { address: "+15555550199", service: "iMessage" }, + pollMessageGuid: poll.pollMessageGuid, + delta: { + type: "voted", + optionIdentifier: poll.options[1].optionIdentifier, + }, + } as LiveEvent; + await t.deliver(vote); + expect( + ( + await issueThreadInteractionService(db).listForIssue( + conversation.issueId, + ) + )[0].status, + ).toBe("pending"); + await t.deliver({ + ...vote, + sequence: 6, + actor: { address: "+15555550101", service: "iMessage" }, + } as LiveEvent); + const resolved = ( + await issueThreadInteractionService(db).listForIssue(conversation.issueId) + )[0]; + expect(resolved.status).toBe("answered"); + expect(resolved.result).toMatchObject({ + answers: [{ questionId: "q", optionIds: ["two"] }], + }); + await t.deliver({ + ...vote, + sequence: 7, + actor: { address: "+15555550101", service: "iMessage" }, + } as LiveEvent); + expect( + await db + .select() + .from(issueQuestionResponseDeliveries) + .where( + eq(issueQuestionResponseDeliveries.interactionId, interaction.id), + ), + ).toHaveLength(1); + }, 30_000); + it("keeps multi-question drafts separate for two linked group participants", async () => { + const t = await setup(); + await t.start(); + await t.qualify(); + const group = photonChat("iMessage;+;draft-group", true); + group.participants.push({ address: "+15555550102", service: "iMessage" }); + t.chats.set(group.guid, group); + await t.deliver(photonEvent(3, group)); + const resource = (await t.service.listResources(t.endpoint.id)).find( + (row) => row.type === "group_chat", + )!; + await t.service.replaceResources( + t.endpoint.id, + [{ id: resource.id, enabled: true }], + t.userId, + ); + await t.deliver(photonEvent(4, group)); + const conversation = ( + await t.service.listConversations(t.endpoint.id) + ).find((row) => !row.isDirectMessage)!; + const second = randomUUID(); + await db.insert(authUsers).values({ + id: second, + name: "Second", + email: `${second}@example.com`, + emailVerified: true, + createdAt: new Date(), + updatedAt: new Date(), + }); + await db.insert(companyMemberships).values({ + companyId: t.companyId, + principalType: "user", + principalId: second, + status: "active", + membershipRole: "operator", + }); + const fromSecond = (sequence: number, text: string) => { + const event = photonEvent(sequence, group, text); + return { + ...event, + message: { + ...event.message, + sender: { address: "+15555550102", service: "iMessage" }, + }, + } as LiveEvent; + }; + await t.deliver(fromSecond(5, "Discover second participant")); + await t.link("+15555550102", second); + const interaction = await issueThreadInteractionService(db).create( + { id: conversation.issueId, companyId: t.companyId }, + { + kind: "ask_user_questions", + continuationPolicy: "none", + payload: { + version: 1, + questions: ["a", "b"].map((id) => ({ + id, + prompt: "Duplicate title", + selectionMode: "single" as const, + required: true, + allowOther: true, + options: [{ id: "text", label: "Type an answer", freeText: true }], + })), + }, + }, + { agentId: t.agentId }, + ); + await t.service.processPendingPublications(); + const [binding] = await db + .select() + .from(chatActions) + .where( + and( + eq(chatActions.endpointId, t.endpoint.id), + eq(chatActions.kind, "photon_interaction"), + eq( + sql`${chatActions.payload}->>'interactionId'`, + interaction.id, + ), + ), + ); + const ref = binding.payload.reference; + await t.deliver(photonEvent(6, group, `/answer ${ref}.1 Alice A`)); + await t.service.processPendingPublications(); + await t.deliver(fromSecond(7, `/answer ${ref}.2 Bob B`)); + await t.deliver(photonEvent(8, group, `/submit ${ref}`)); + expect( + ( + await issueThreadInteractionService(db).listForIssue( + conversation.issueId, + ) + )[0].status, + ).toBe("pending"); + await t.service.processPendingPublications(); + expect(t.f.client.messages.sendText.mock.calls.some((call) => + call[1].includes(`Send /answer ${ref}.2 to correct it.`))).toBe(true); + await t.deliver(photonEvent(9, group, `/answer ${ref}.2 Alice B`)); + await t.deliver(photonEvent(10, group, `/submit ${ref}`)); + const resolved = ( + await issueThreadInteractionService(db).listForIssue(conversation.issueId) + )[0]; + expect(resolved.result).toMatchObject({ + answers: [ + { questionId: "a", otherText: "Alice A" }, + { questionId: "b", otherText: "Alice B" }, + ], + }); + expect(resolved.resolvedByUserId).toBe(t.userId); + const comments = await db + .select() + .from(issueComments) + .where(eq(issueComments.issueId, conversation.issueId)); + expect( + comments.some( + (row) => row.body.includes("/answer") || row.body.includes("/submit"), + ), + ).toBe(false); + }, 30_000); + it("requires a correlated rejection reason and emits only one canonical continuation", async () => { + const t = await setup(); + const conversation = await t.start(); + await t.qualify(); + const interaction = await issueThreadInteractionService(db).create( + { id: conversation.issueId, companyId: t.companyId }, + { + kind: "request_confirmation", + continuationPolicy: "wake_assignee", + payload: { + version: 1, + prompt: "Publish approved summary?", + rejectRequiresReason: true, + }, + }, + { agentId: t.agentId }, + ); + await t.service.processPendingPublications(); + const [binding] = await db + .select() + .from(chatActions) + .where( + and( + eq(chatActions.endpointId, t.endpoint.id), + eq(chatActions.kind, "photon_interaction"), + eq( + sql`${chatActions.payload}->>'interactionId'`, + interaction.id, + ), + ), + ); + const ref = binding.payload.reference; + await t.deliver(photonEvent(3, t.f.chat, `/answer ${ref} Reject`)); + expect( + ( + await issueThreadInteractionService(db).listForIssue( + conversation.issueId, + ) + )[0].status, + ).toBe("pending"); + await t.service.processPendingPublications(); + await t.deliver( + photonEvent(4, t.f.chat, `/answer ${ref} Reject Needs another review`), + ); + await t.service.processPendingPublications(); + expect( + ( + await issueThreadInteractionService(db).listForIssue( + conversation.issueId, + ) + )[0], + ).toMatchObject({ + status: "rejected", + result: { reason: "Needs another review" }, + }); + await t.deliver(photonEvent(5, t.f.chat, `/answer ${ref} Accept`)); + expect( + await db + .select() + .from(chatActions) + .where( + and( + eq(chatActions.endpointId, t.endpoint.id), + eq(chatActions.kind, "interaction_wakeup"), + eq( + sql`${chatActions.payload}->>'interactionId'`, + interaction.id, + ), + ), + ), + ).toHaveLength(1); + }, 30_000); + it("retries a delayed HEIC after restart without another comment or a premature wake", async () => { + const t = await setup(); + const conversation = await t.start(); + await t.qualify(); + const body = await readFile( + new URL("./fixtures/synthetic.heic", import.meta.url), + ); + const event = photonEvent(3, t.f.chat, ""); + event.message.content.attachments.push({ + guid: "photo-guid", + fileName: "photo.heic", + mimeType: "image/heic", + totalBytes: body.length, + isHidden: false, + isSticker: false, + } as any); + t.f.client.attachments.downloadStream.mockImplementationOnce(() => + stream([]), + ); + await t.deliver(event); + expect(t.wakeup).toHaveBeenCalledTimes(1); + const [delivery] = await db + .select() + .from(chatDeliveries) + .where( + and( + eq(chatDeliveries.endpointId, t.endpoint.id), + eq( + sql`${chatDeliveries.normalizedEvent}#>>'{message,providerMessageId}'`, + event.message.guid, + ), + ), + ); + expect(delivery.state).toBe("retry"); + expect( + delivery.normalizedEvent.message.attachments[0].recovery.locator, + ).toMatchObject({ + kind: "photon_attachment", + messageGuid: event.message.guid, + attachmentGuid: "photo-guid", + }); + const service = await t.restart(); + t.f.client.attachments.downloadStream.mockImplementation(() => + stream([ + { + type: "header", + info: { guid: "photo-guid", totalBytes: body.length }, + companionInfo: { + kind: "live-photo-video", + mimeType: "video/quicktime", + fileName: "photo.mov", + totalBytes: 9, + }, + }, + { type: "primaryChunk", data: body }, + { type: "companionChunk", data: Buffer.from("companion") }, + ]), + ); + await db + .update(chatDeliveries) + .set({ nextAttemptAt: new Date(0) }) + .where(eq(chatDeliveries.id, delivery.id)); + await service.processPendingDeliveries(); + expect(t.wakeup).toHaveBeenCalledTimes(2); + const links = await db + .select() + .from(chatMessageLinks) + .where( + and( + eq(chatMessageLinks.endpointId, t.endpoint.id), + eq(chatMessageLinks.providerMessageId, event.message.guid), + ), + ); + expect(links).toHaveLength(1); + const files = await db + .select({ contentType: assets.contentType }) + .from(issueAttachments) + .innerJoin(assets, eq(assets.id, issueAttachments.assetId)) + .where(eq(issueAttachments.issueCommentId, links[0].commentId!)); + expect(files.map((file) => file.contentType).sort()).toEqual([ + "image/heic", + "image/jpeg", + "video/quicktime", + ]); + expect( + await db + .select() + .from(chatActions) + .where( + and( + eq(chatActions.endpointId, t.endpoint.id), + eq(chatActions.kind, "attachment_derivative"), + ), + ), + ).toHaveLength(1); + }, 30_000); + + it.each([false, true])("keeps completed DM replies on one task through restart and publishes committed comments live (shared=%s)", async (shared) => { + const t = await setup(shared); + const original = await t.start(); + await t.qualify(); + await issueService(db).update(original.issueId, { status: "done", actorUserId: t.userId }); + // Recover rows that the previous implementation marked completed without + // an explicit control. A restart must not require an SDK chat cache. + await db.update(chatConversations).set({ state: "completed" }).where(eq(chatConversations.id, original.id)); + const service = await t.restart(); + const visibleComments: Promise[] = []; + const unsubscribe = subscribeCompanyLiveEvents(t.companyId, (event) => { + if (event.type === "activity.logged" && event.payload.action === "issue.comment_added") { + expect(event.payload.entityId).toBe(original.issueId); + const details = event.payload.details as { commentId: string }; + visibleComments.push(db.select().from(issueComments).where(eq(issueComments.id, details.commentId))); + } + }); + try { + const followUp = photonEvent(3, t.f.chat, "Continue our conversation"); + await t.deliver(followUp); + await t.deliver(followUp); + expect(await service.listConversations(t.endpoint.id)).toMatchObject([{ id: original.id, issueId: original.issueId, state: "active" }]); + expect((await db.select().from(issues).where(eq(issues.id, original.issueId)))[0].status).toBe("todo"); + expect(t.wakeup).toHaveBeenCalledTimes(2); + expect(visibleComments).toHaveLength(1); + expect(await visibleComments[0]).toMatchObject([{ issueId: original.issueId, body: "Continue our conversation", metadata: { sourceChannel: "imessage-photon" } }]); + expect(await db.select().from(activityLog).where(and(eq(activityLog.entityId, original.issueId), eq(activityLog.action, "issue.comment_added")))).toHaveLength(2); + } finally { + unsubscribe(); + } + await issueService(db).update(original.issueId, { status: "done", actorUserId: t.userId }); + await t.deliver(photonEvent(4, t.f.chat, "/status")); + await service.processPendingPublications(); + expect(t.f.client.messages.sendText.mock.calls.some((call) => call[1].includes(original.issueIdentifier!))).toBe(true); + await t.deliver(photonEvent(5, t.f.chat, "/new")); + await service.processPendingPublications(); + await t.deliver(photonEvent(6, t.f.chat, "An explicitly new task")); + const conversations = await service.listConversations(t.endpoint.id); + expect(conversations).toHaveLength(2); + expect(conversations.find((row) => row.id !== original.id)?.issueId).not.toBe(original.issueId); + }, 30_000); + + it("preserves reply context, rejects old quoted controls, and starts a new generation after close", async () => { + const t = await setup(); + const original = await t.start(); + await t.qualify(); + const quoted = photonEvent(3, t.f.chat, "Follow up to that message"); + Object.assign(quoted.message, { + replyTargetGuid: "message-2", + threadOriginatorPart: "0", + }); + await t.deliver(quoted); + const [link] = await db + .select() + .from(chatMessageLinks) + .where( + and( + eq(chatMessageLinks.endpointId, t.endpoint.id), + eq(chatMessageLinks.providerMessageId, "message-3"), + ), + ); + const [comment] = await db + .select() + .from(issueComments) + .where(eq(issueComments.id, link.commentId!)); + expect(JSON.stringify(comment.metadata)).toContain("Reply to message"); + expect(JSON.stringify(comment.metadata)).toContain("message-2"); + await t.deliver(photonEvent(4, t.f.chat, "/close")); + await t.service.processPendingPublications(); + await t.deliver(photonEvent(5, t.f.chat, "Start the next task")); + const conversations = await t.service.listConversations(t.endpoint.id); + expect(conversations).toHaveLength(2); + const current = conversations.find((row) => row.id !== original.id)!; + const stale = photonEvent(6, t.f.chat, "/close"); + Object.assign(stale.message, { replyTargetGuid: "message-2" }); + await t.deliver(stale); + await t.service.processPendingPublications(); + expect( + (await t.service.listConversations(t.endpoint.id)).find( + (row) => row.id === current.id, + )?.state, + ).toBe("active"); + await t.deliver(photonEvent(7, t.f.chat, "/status")); + await t.service.processPendingPublications(); + expect( + t.f.client.messages.sendText.mock.calls.some((call) => + call[1].includes("Start the next task"), + ), + ).toBe(true); + }, 30_000); + it("retains accepted pending input through pause and blocks publication after group removal", async () => { + const t = await setup(); + await t.start(); + await t.qualify(); + await t.callbacks().onPhotonEvent!( + photonEvent(3, t.f.chat, "Accepted before pause"), + ); + await t.service.configure(t.endpoint.id, { action: "pause" }, t.userId); + await t.service.processPendingDeliveries(); + expect(t.wakeup).toHaveBeenCalledTimes(1); + await t.service.configure(t.endpoint.id, { action: "resume" }, t.userId); + await t.service.processPendingDeliveries(); + expect(t.wakeup).toHaveBeenCalledTimes(2); + const group = photonChat("iMessage;+;remove-group", true); + t.chats.set(group.guid, group); + await t.deliver(photonEvent(4, group)); + const resource = (await t.service.listResources(t.endpoint.id)).find( + (row) => row.type === "group_chat", + )!; + await t.service.replaceResources( + t.endpoint.id, + [{ id: resource.id, enabled: true }], + t.userId, + ); + await t.deliver(photonEvent(5, group)); + const get = t.f.client.chats.get; + get.mockRejectedValueOnce(new Error("Chat inaccessible after removal")); + await t.deliver({ + type: "group.changed", + chatGuid: group.guid, + sequence: 6, + occurredAt: new Date(), + isFromMe: false, + change: { + type: "participantRemoved", + participant: { address: "+15555550100", service: "iMessage" }, + }, + } as LiveEvent); + expect( + (await t.service.listResources(t.endpoint.id)).find( + (row) => row.id === resource.id, + )?.availability, + ).toBe("unavailable"); + // Removal and late events need no now-inaccessible chat lookup. + const calls = get.mock.calls.length; + await t.deliver(photonEvent(7, group, "Late event after removal")); + expect(get).toHaveBeenCalledTimes(calls); + const undiscovered = photonChat("iMessage;+;undiscovered-removed-group", true); + await t.deliver({ + type: "group.changed", + chatGuid: undiscovered.guid, + sequence: 8, + occurredAt: new Date(), + isFromMe: false, + change: { + type: "participantLeft", + participant: { address: "+15555550100", service: "iMessage" }, + }, + } as LiveEvent); + await t.deliver(photonEvent(9, undiscovered, "Late undiscovered event")); + expect(get).toHaveBeenCalledTimes(calls); + expect((await t.service.listResources(t.endpoint.id)).filter( + (row) => row.type === "group_chat" && row.availability === "unavailable", + )).toHaveLength(2); + }, 30_000); + it.each(["active", "verifying"] as const)("reconstructs native continuation authority for a linked group responder (%s)", async (endpointStatus) => { + const t = await setup(); + await t.start(); + await t.qualify(); + const group = photonChat("iMessage;+;native-group", true); + group.participants.push({ address: "+15555550102", service: "iMessage" }); + t.chats.set(group.guid, group); + await t.deliver(photonEvent(3, group)); + const resource = (await t.service.listResources(t.endpoint.id)).find( + (row) => row.type === "group_chat", + )!; + await t.service.replaceResources( + t.endpoint.id, + [{ id: resource.id, enabled: true }], + t.userId, + ); + await t.deliver(photonEvent(4, group)); + const conversation = ( + await t.service.listConversations(t.endpoint.id) + ).find((row) => !row.isDirectMessage)!; + const second = randomUUID(); + await db.insert(authUsers).values({ + id: second, + name: "Responder", + email: `${second}@example.com`, + emailVerified: true, + createdAt: new Date(), + updatedAt: new Date(), + }); + await db.insert(companyMemberships).values({ + companyId: t.companyId, + principalType: "user", + principalId: second, + status: "active", + membershipRole: "operator", + }); + const discover = photonEvent(5, group); + Object.assign(discover.message, { + sender: { address: "+15555550102", service: "iMessage" }, + }); + await t.deliver(discover); + await t.link("+15555550102", second); + const [sourceLink] = await db + .select() + .from(chatMessageLinks) + .where( + and( + eq(chatMessageLinks.endpointId, t.endpoint.id), + eq(chatMessageLinks.providerMessageId, "message-4"), + ), + ); + const sourceRunId = randomUUID(); + const sourceCommentId = sourceLink.commentId!; + await db.insert(heartbeatRuns).values({ + id: sourceRunId, + companyId: t.companyId, + agentId: t.agentId, + status: "succeeded", + runtimeMode: "native", + nativeIssueId: conversation.issueId, + contextSnapshot: { + source: "chat:imessage-photon", + issueId: conversation.issueId, + wakeCommentId: sourceCommentId, + wakeCommentIds: [sourceCommentId], + paperclipExternalChatExecutionBound: true, + paperclipWake: { + externalChatProvider: "imessage-photon", + externalChatExecutionBound: true, + }, + }, + }); + const interaction = await issueThreadInteractionService(db).create( + { id: conversation.issueId, companyId: t.companyId }, + { + kind: "ask_user_questions", + continuationPolicy: "wake_assignee", + sourceRunId, + sourceCommentId, + payload: { + version: 1, + questions: [ + { + id: "priority", + prompt: "Priority?", + selectionMode: "single", + required: true, + allowOther: false, + options: [ + { id: "one", label: "One" }, + { id: "two", label: "Two" }, + ], + }, + ], + }, + }, + { agentId: t.agentId, runId: sourceRunId }, + ); + await t.service.processPendingPublications(); + if (endpointStatus === "verifying") { + await db.update(chatEndpoints).set({status: "verifying", setup: sql`jsonb_set(${chatEndpoints.setup}, '{step}', '"test"')`}).where(eq(chatEndpoints.id, t.endpoint.id)); + } + const [action] = await db + .select() + .from(chatActions) + .where( + and( + eq(chatActions.endpointId, t.endpoint.id), + eq(chatActions.kind, "photon_interaction"), + eq( + sql`${chatActions.payload}->>'interactionId'`, + interaction.id, + ), + ), + ); + const reply = photonEvent( + 6, + group, + `/answer ${action.payload.reference} 2`, + ); + Object.assign(reply.message, { + sender: { address: "+15555550102", service: "iMessage" }, + }); + await t.deliver(reply); + expect( + ( + await issueThreadInteractionService(db).listForIssue( + conversation.issueId, + ) + )[0].resolvedByUserId, + ).toBe(second); + const runId = randomUUID(), + wakeId = randomUUID(); + const context = { + source: "issue.interaction.respond", + issueId: conversation.issueId, + wakeReason: "issue_commented", + interactionId: interaction.id, + interactionKind: "ask_user_questions", + interactionStatus: "answered", + sourceRunId, + sourceCommentId, + wakeCommentId: sourceCommentId, + wakeCommentIds: [sourceCommentId], + externalChatContinuation: true, + }; + await db.insert(agentWakeupRequests).values({ + id: wakeId, + companyId: t.companyId, + agentId: t.agentId, + source: "automation", + status: "running", + runId, + requestedByActorType: "user", + requestedByActorId: second, + idempotencyKey: `question-response:${interaction.id}`, + payload: context, + }); + await db.insert(heartbeatRuns).values({ + id: runId, + companyId: t.companyId, + agentId: t.agentId, + status: "running", + runtimeMode: "native", + nativeIssueId: conversation.issueId, + wakeupRequestId: wakeId, + contextSnapshot: context, + }); + await db + .update(issueQuestionResponseDeliveries) + .set({ + status: "fallback_queued", + deliveryMode: "wake_fallback", + targetRunId: runId, + }) + .where(eq(issueQuestionResponseDeliveries.interactionId, interaction.id)); + const proof = await resolveExternalChatQuestionResponse( + db, + { + companyId: t.companyId, + agentId: t.agentId, + issueId: conversation.issueId, + runId, + }, + context, + "read", + true, + ); + expect(proof).toMatchObject({ + provider: "imessage-photon", + marker: { sourceCommentId, conversationId: conversation.id }, + }); + await db + .update(chatIdentityLinks) + .set({ status: "revoked" }) + .where( + and( + eq(chatIdentityLinks.endpointId, t.endpoint.id), + eq(chatIdentityLinks.paperclipUserId, second), + ), + ); + expect( + await resolveExternalChatQuestionResponse( + db, + { + companyId: t.companyId, + agentId: t.agentId, + issueId: conversation.issueId, + runId, + }, + context, + "read", + true, + ), + ).toBeNull(); + }, 30_000); + it("fences checkpoint writes in the transaction that verifies receiver ownership", async () => { + const t = await setup(); + await t.start(); + await t.qualify(); + const callback = t.callbacks().onPhotonCheckpoint!; + const key = `photon:${createHash("sha256").update("checkpoint").digest("hex")}`; + const before = await db + .select() + .from(chatSdkState) + .where( + and( + eq(chatSdkState.endpointId, t.endpoint.id), + eq(chatSdkState.stateKey, key), + ), + ); + await db + .update(chatEndpointLeases) + .set({ token: randomUUID() }) + .where( + and( + eq(chatEndpointLeases.endpointId, t.endpoint.id), + eq(chatEndpointLeases.leaseKey, "photon_receiver_runtime"), + ), + ); + await expect(callback(100)).rejects.toThrow(); + const after = await db + .select() + .from(chatSdkState) + .where( + and( + eq(chatSdkState.endpointId, t.endpoint.id), + eq(chatSdkState.stateKey, key), + ), + ); + expect(after.map((row) => row.value)).toEqual( + before.map((row) => row.value), + ); + }, 30_000); + it("answers status without creating a task after a conversation closes", async () => { + const t = await setup(); + await t.start(); + await t.qualify(); + await t.deliver(photonEvent(3, t.f.chat, "/close")); + await t.service.processPendingPublications(); + const wakes = t.wakeup.mock.calls.length; + await t.deliver(photonEvent(4, t.f.chat, "/status")); + await t.service.processPendingPublications(); + expect(t.wakeup).toHaveBeenCalledTimes(wakes); + expect(await t.service.listConversations(t.endpoint.id)).toHaveLength(1); + expect(t.f.client.messages.sendText.mock.calls.at(-1)?.[1]).toContain( + "No task is active", + ); + }, 30_000); +}); diff --git a/server/src/__tests__/photon/fixture.ts b/server/src/__tests__/photon/fixture.ts new file mode 100644 index 0000000000..3effe85200 --- /dev/null +++ b/server/src/__tests__/photon/fixture.ts @@ -0,0 +1,222 @@ +import { vi } from "vitest"; +import { + TypedEventStream, + type GrpcAdvancedIMessage, + type Chat, + type Message, + type LiveEvent, + type CatchUpEvent, +} from "@photon-ai/advanced-imessage"; +import type { + ChatSdkStatePersistence, + ChatSdkStateRecord, +} from "../../services/chat-sdk-state.js"; +import { PhotonState } from "../../services/photon/state.js"; +import { + PhotonCloudClient, + PhotonLineAuthentication, +} from "../../services/photon/cloud.js"; +import { + PhotonChatAdapter, + photonThreadId, +} from "../../services/photon/adapter.js"; +export function memoryPersistence(): ChatSdkStatePersistence { + const rows = new Map(); + const key = (scope: { companyId: string; endpointId: string }, key: string) => + `${scope.companyId}:${scope.endpointId}:${key}`; + return { + async read(scope, id) { + return structuredClone(rows.get(key(scope, id)) ?? null); + }, + async compareAndSet(input) { + const id = key(input, input.key), + current = rows.get(id); + if ((current?.version ?? null) !== input.expectedVersion) return false; + rows.set(id, { + value: structuredClone(input.value), + version: (current?.version ?? 0) + 1, + expiresAt: input.expiresAt, + }); + return true; + }, + async deleteIfVersion(input) { + const id = key(input, input.key); + if (rows.get(id)?.version !== input.expectedVersion) return false; + return rows.delete(id); + }, + }; +} +export function stream(values: T[]): TypedEventStream { + return new TypedEventStream( + (async function* () { + for (const value of values) yield value; + })(), + async () => {}, + ); +} +export function photonChat( + guid = "iMessage;-;+15555550101", + isGroup = false, +): Chat { + return { + guid, + service: "iMessage", + isGroup, + isArchived: false, + displayName: isGroup ? "Test group" : "", + participants: [{ address: "+15555550101", service: "iMessage" }], + properties: {}, + } as unknown as Chat; +} +export function photonMessage( + guid: string, + chatGuid: string, + text = "Hello", + fromMe = false, +): Message { + return { + guid, + chatGuids: [chatGuid], + content: { text, attachments: [], parts: [], mentions: [] }, + sender: { address: "+15555550101", service: "iMessage" }, + dateCreated: new Date(), + isFromMe: fromMe, + isSystemMessage: false, + isServiceMessage: false, + } as unknown as Message; +} +export function photonEvent( + sequence: number, + chat = photonChat(), + text = "Hello", +): LiveEvent { + return { + type: "message.received", + chatGuid: chat.guid, + message: photonMessage(`message-${sequence}`, chat.guid, text), + sequence, + occurredAt: new Date(), + isFromMe: false, + } as LiveEvent; +} +export function photonFixture() { + const persistence = memoryPersistence(); + const state = new PhotonState( + { companyId: "company", endpointId: "endpoint" }, + persistence, + ); + const cloud = new PhotonCloudClient(); + const allocation = vi.spyOn(cloud, "allocation").mockResolvedValue({ + inspection: { + projectId: "project", + projectName: "Tests", + allocation: "dedicated", + eligible: true, + lines: [{ lineId: "line", phoneNumber: "+15555550100", eligible: true }], + }, + tokens: new Map([["line", "private-line-token"]]), + expiresIn: 300, + }); + const authentication = new PhotonLineAuthentication( + { projectId: "project", lineId: "line", phoneNumber: "+15555550100" }, + "private-project-secret", + cloud, + ); + const chat = photonChat(); + const receipts = new Map(); + const polls = new Map< + string, + { + pollMessageGuid: string; + options: { optionIdentifier: string; text: string }[]; + } + >(); + const events: CatchUpEvent[] = []; + const sendText = vi.fn( + async ( + chatGuid: string, + text: string, + opts: { clientMessageId: string }, + ) => { + if (!receipts.has(opts.clientMessageId)) + receipts.set( + opts.clientMessageId, + photonMessage(`sent-${receipts.size}`, chatGuid, text, true), + ); + return receipts.get(opts.clientMessageId)!; + }, + ); + const client = { + close: vi.fn(async () => {}), + chats: { + get: vi.fn(async () => chat), + setTyping: vi.fn(async () => {}), + subscribeEvents: vi.fn(() => stream([])), + }, + messages: { + sendText, + sendAttachment: vi.fn( + async ( + chatGuid: string, + attachment: string, + opts: { clientMessageId: string }, + ) => sendText(chatGuid, attachment, opts), + ), + edit: vi.fn(), + get: vi.fn(async (id) => photonMessage(id, chat.guid)), + subscribeEvents: vi.fn(() => stream([])), + }, + attachments: { + upload: vi.fn(async () => ({ attachment: { guid: "uploaded-file" } })), + downloadStream: vi.fn(), + }, + groups: { subscribeEvents: vi.fn(() => stream([])) }, + polls: { + create: vi.fn( + async ( + _chat: string, + _title: string, + labels: string[], + opts: { clientMessageId: string }, + ) => { + if (!polls.has(opts.clientMessageId)) + polls.set(opts.clientMessageId, { + pollMessageGuid: `poll-${polls.size}`, + options: labels.map((text, index) => ({ + optionIdentifier: `${polls.size}-option-${index}`, + text, + })), + }); + return polls.get(opts.clientMessageId)!; + }, + ), + subscribeEvents: vi.fn(() => stream([])), + }, + events: { catchUp: vi.fn(() => stream(events)) }, + }; + const adapter = new PhotonChatAdapter( + "Agent", + authentication, + state, + client as unknown as GrpcAdvancedIMessage, + ); + const threadId = photonThreadId({ + lineId: "line", + chatGuid: chat.guid, + isGroup: false, + }); + return { + state, + persistence, + allocation, + cloud, + authentication, + chat, + client, + adapter, + threadId, + receipts, + events, + polls, + }; +} diff --git a/server/src/__tests__/photon/fixtures/README.md b/server/src/__tests__/photon/fixtures/README.md new file mode 100644 index 0000000000..751a635820 --- /dev/null +++ b/server/src/__tests__/photon/fixtures/README.md @@ -0,0 +1 @@ +Synthetic HEIC fixture generated on macOS from a 16 × 16 solid RGB (40, 120, 180) PNG with `sips -s format heic`. Contains no personal photo or metadata. The test exercises the installed heif2jpeg native binary. This does not replace qualification with an actual iPhone photo. diff --git a/server/src/__tests__/photon/fixtures/synthetic.heic b/server/src/__tests__/photon/fixtures/synthetic.heic new file mode 100644 index 0000000000..c21dfc57cb Binary files /dev/null and b/server/src/__tests__/photon/fixtures/synthetic.heic differ diff --git a/server/src/__tests__/photon/photon.test.ts b/server/src/__tests__/photon/photon.test.ts new file mode 100644 index 0000000000..551776439b --- /dev/null +++ b/server/src/__tests__/photon/photon.test.ts @@ -0,0 +1,712 @@ +import { describe, it, expect, vi } from "vitest"; +import { Client, Server, ServerCredentials, credentials } from "@grpc/grpc-js"; +import { IMessageError } from "@photon-ai/advanced-imessage"; +import type { AskUserQuestionsInteraction } from "@paperclipai/shared"; +import { + PhotonCloudClient, + PhotonLineAuthentication, photonSharedIdentity, photonSharedScope, + PhotonError, + photonFailure, +} from "../../services/photon/cloud.js"; +import { PhotonReceiver } from "../../services/photon/receiver.js"; +import { + PhotonChatAdapter, + splitPhotonText, +} from "../../services/photon/adapter.js"; +import { PhotonState } from "../../services/photon/state.js"; +import { + photonCatchUpRequest, + photonEnvelopeSequence, + decodePhotonRecoveryFrame, + PhotonRecoveryTransport, + PHOTON_CATCHUP_PATH, +} from "../../services/photon/recovery-transport.js"; +import { + publishPhotonPrompt, + parsePhotonQuestionAnswer, + photonResponseCommand, +} from "../../services/photon/interactions.js"; +import { photonFixture, photonEvent, stream } from "./fixture.js"; +import { + downloadPhotonAttachment, + takePhotonCompanion, +} from "../../services/photon/attachments.js"; +import { + photonHeifPreview, + validateHeifDimensions, + validatePhotonImage, +} from "../../services/photon/media.js"; +import sharp from "sharp"; +import { readFile } from "node:fs/promises"; + +const question = (id = "interaction"): AskUserQuestionsInteraction => + ({ + id, + kind: "ask_user_questions", + payload: { + questions: [ + { + id: "q1", + prompt: "Same title", + options: [ + { id: "a", label: "Same" }, + { id: "b", label: "Same" }, + ], + selectionMode: "single", + allowOther: false, + required: true, + }, + ], + }, + }) as AskUserQuestionsInteraction; +const guard = async () => {}; + +describe("Photon Cloud and fixed line identity", () => { + it("uses Basic project auth, verifies dedicated allocation, and never exposes tokens", async () => { + const fetcher = vi.fn( + async (url: string | URL | Request, init?: RequestInit) => { + expect(String(url)).toMatch( + /^https:\/\/spectrum.photon.codes\/projects\/p\//, + ); + expect(new Headers(init?.headers).get("authorization")).toBe( + `Basic ${Buffer.from("p:secret").toString("base64")}`, + ); + return Response.json({ + succeed: true, + data: String(url).endsWith("tokens") + ? { + type: "dedicated", + auth: { one: "TOKEN", two: "TOKEN2" }, + numbers: { one: "+15555550100", two: "+15555550102" }, + expiresIn: 300, + } + : { id: "p", name: "Project" }, + }); + }, + ); + const result = await new PhotonCloudClient(fetcher).inspect("p", "secret"); + expect(result.lines).toHaveLength(2); + expect(JSON.stringify(result)).not.toMatch(/TOKEN|secret/); + }); + it("rejects credentials and missing dedicated lines while inspecting shared DMs without exposing tokens", async () => { + const client = new PhotonCloudClient( + async () => new Response("secret=BAD", { status: 401 }), + ); + await expect(client.inspect("p", "secret")).rejects.toMatchObject({ + code: "credentials", + }); + const shared = new PhotonCloudClient(async () => + Response.json({ + succeed: true, + data: { type: "shared", token: "PRIVATE", expiresIn: 300 }, + }), + ); + expect(await shared.inspect("p", "secret")).toMatchObject({ + eligible: true, + allocation: "shared", + lines: [], + }); + const missing = new PhotonCloudClient(async () => + Response.json({ + succeed: true, + data: { type: "dedicated", auth: {}, numbers: {}, expiresIn: 300 }, + }), + ); + expect(await missing.inspect("p", "secret")).toMatchObject({ + eligible: false, + lines: [], + }); + }); + it("binds shared gateway tokens to one project and fences renewal and group access", async () => { + let now = 0; + const cloud = new PhotonCloudClient(); + const allocation = vi.spyOn(cloud, "allocation").mockResolvedValue({ + inspection: {projectId: "p", projectName: "Shared", allocation: "shared", eligible: true, lines: []}, + tokens: new Map(), sharedToken: "first", expiresIn: 60, + }); + const identity = {allocation: "shared" as const, projectId: "p", lineId: photonSharedScope("p"), phoneNumber: photonSharedIdentity("p")}; + const auth = new PhotonLineAuthentication(identity, "secret", cloud, () => now); + expect(auth.address).toBe("imessage.spectrum.photon.codes:443"); + await expect(auth.token()).resolves.toBe("first"); + expect(photonSharedScope("other")).not.toBe(identity.lineId); + expect(() => new PhotonLineAuthentication({...identity, projectId: "other"}, "secret", cloud)).toThrow(/match its project/); + const f = photonFixture(); + const adapter = new PhotonChatAdapter("Shared", auth, f.state, f.adapter.client); + expect(() => adapter.encodeThreadId({lineId: identity.lineId, chatGuid: "group", isGroup: true})).toThrow(/direct messages only/); + expect(() => adapter.decodeThreadId(`imessage-photon:${identity.lineId}:g:Z3JvdXA`)).toThrow(/direct messages only/); + now = 60_000; + allocation.mockResolvedValueOnce({inspection: {projectId:"p", projectName:"Moved", allocation:"dedicated", eligible:true, lines:[]}, tokens:new Map(), expiresIn:60}); + await expect(auth.token()).rejects.toMatchObject({code:"line_unavailable"}); + }); + it("renews only the selected line and refuses replacement identity or retired ownership", async () => { + const f = photonFixture(); + await expect(f.authentication.token()).resolves.toBe("private-line-token"); + f.authentication.retire(); + await expect(f.authentication.token()).rejects.toMatchObject({ + code: "credentials", + }); + const other = photonFixture(); + other.allocation.mockResolvedValueOnce({ + inspection: { + projectId: "project", + projectName: "Test", + allocation: "dedicated", + eligible: true, + lines: [ + { lineId: "line", phoneNumber: "+15555550999", eligible: true }, + ], + }, + tokens: new Map([["line", "TOKEN"]]), + expiresIn: 60, + }); + await expect(other.authentication.token()).rejects.toMatchObject({ + code: "line_unavailable", + }); + }); +}); + +describe("Photon publication receipts", () => { + it("keeps Unicode and paragraph order and reuses immutable receipts across restart", async () => { + const f = photonFixture(); + const text = "😀".repeat(3999) + "\n\n" + "tail"; + expect(splitPhotonText(text).join("")).toBe(text); + expect( + splitPhotonText(text).every((part) => Array.from(part).length <= 4000), + ).toBe(true); + const first = await f.adapter.publish( + f.threadId, + "pub", + { markdown: text }, + { assertCurrent: guard }, + ); + const restarted = new PhotonChatAdapter( + "Agent", + f.authentication, + new PhotonState(f.state.scope, f.persistence), + f.adapter.client, + ); + expect( + await restarted.publish( + f.threadId, + "pub", + { markdown: text }, + { assertCurrent: guard }, + ), + ).toEqual(first); + expect(f.client.messages.sendText).toHaveBeenCalledTimes(2); + await expect( + restarted.publish( + f.threadId, + "pub", + { markdown: "changed" }, + { assertCurrent: guard }, + ), + ).rejects.toThrow("payload changed"); + }); + it("holds an unknown send until explicit retry and reuses the same key", async () => { + const f = photonFixture(); + const original = f.client.messages.sendText.getMockImplementation()!; + f.client.messages.sendText.mockImplementationOnce(async (...args) => { + await original(...args); + throw new Error("socket closed after send"); + }); + await expect( + f.adapter.publish(f.threadId, "pub", "hello", { assertCurrent: guard }), + ).rejects.toMatchObject({ code: "delivery_unknown" }); + await expect( + f.adapter.publish(f.threadId, "pub", "hello", { assertCurrent: guard }), + ).rejects.toMatchObject({ code: "delivery_unknown" }); + expect(f.client.messages.sendText).toHaveBeenCalledTimes(1); + await f.adapter.publish(f.threadId, "pub", "hello", { + assertCurrent: guard, + retryUnknown: true, + }); + expect(f.receipts.size).toBe(1); + expect(f.client.messages.sendText.mock.calls[0][2]).toEqual( + f.client.messages.sendText.mock.calls[1][2], + ); + }); + it("stores an upload before a failed send and never uploads it twice", async () => { + const f = photonFixture(); + f.client.messages.sendAttachment.mockRejectedValueOnce( + new Error("lost receipt"), + ); + const message = { + markdown: "", + files: [{ filename: "photo.png", data: Buffer.from("data") }], + }; + await expect( + f.adapter.publish(f.threadId, "filepub", message, { + assertCurrent: guard, + }), + ).rejects.toMatchObject({ code: "delivery_unknown" }); + await f.adapter.publish(f.threadId, "filepub", message, { + assertCurrent: guard, + retryUnknown: true, + }); + expect(f.client.attachments.upload).toHaveBeenCalledTimes(1); + }); + it("retains unknown delivery when the shared gateway rejects a duplicate without a receipt", async () => { + const f = photonFixture(); + f.client.messages.sendText.mockRejectedValueOnce(new Error("lost receipt")); + await expect(f.adapter.publish(f.threadId, "duplicate", "hello", { + assertCurrent: guard, + })).rejects.toMatchObject({ code: "delivery_unknown" }); + // Observed on Photon Pro: ALREADY_EXISTS arrives as internalError, without + // a message GUID. Neither the wording nor duplicate status proves a receipt. + f.client.messages.sendText.mockRejectedValueOnce(new IMessageError( + "[upstream] Operation already processed with this client message ID", + { code: "internalError", grpcCode: 6, retryable: false }, + )); + await expect(f.adapter.publish(f.threadId, "duplicate", "hello", { + assertCurrent: guard, retryUnknown: true, + })).rejects.toMatchObject({ code: "delivery_unknown" }); + await expect(f.adapter.publish(f.threadId, "duplicate", "hello", { + assertCurrent: guard, + })).rejects.toMatchObject({ code: "delivery_unknown" }); + expect(f.client.messages.sendText).toHaveBeenCalledTimes(2); + expect(f.client.messages.sendText.mock.calls[0][2]).toEqual( + f.client.messages.sendText.mock.calls[1][2], + ); + }); + it("distinguishes explicit quota errors and rechecks authorization between parts", async () => { + const f = photonFixture(); + f.client.messages.sendText.mockRejectedValueOnce( + new IMessageError("secret upstream text", { + code: "dailyLimitExceeded", + grpcCode: 8, + retryable: true, + retryAfter: 12345, + }), + ); + await expect( + f.adapter.publish(f.threadId, "quota", "hello", { assertCurrent: guard }), + ).rejects.toMatchObject({ code: "quota", retryAfterMs: 12345 }); + await f.adapter.publish(f.threadId, "quota", "hello", { + assertCurrent: guard, + }); + let calls = 0; + await expect( + f.adapter.publish(f.threadId, "parts", "x".repeat(9000), { + assertCurrent: async () => { + if (++calls === 2) throw new Error("revoked"); + }, + }), + ).rejects.toThrow("revoked"); + expect(f.client.messages.sendText).toHaveBeenCalledTimes(3); + }); +}); + +describe("Photon durable event recovery", () => { + it("records preceding events, deduplicates, and does not checkpoint an unadmitted event", async () => { + const f = photonFixture(); + const events = [photonEvent(1), photonEvent(2)]; + let crash = true; + const admitted = vi.fn(async (event) => { + if (event.sequence === 2 && crash) throw new Error("crash"); + }); + const receiver = new PhotonReceiver({ + client: f.adapter.client, + state: f.state, + lineId: "line", + intakeAfter: 0, + assertOwned: guard, + admit: admitted, + failure: guard, + catchUp: () => + stream([...events, { type: "catchup.complete", headSequence: 2 }]), + }); + await expect(receiver.catchUp()).rejects.toThrow("crash"); + expect(await f.state.read("checkpoint")).toMatchObject({ sequence: 1 }); + crash = false; + await receiver.catchUp(); + expect(await f.state.read("checkpoint")).toMatchObject({ sequence: 2 }); + expect(admitted.mock.calls.map(([event]) => event.sequence)).toEqual([ + 1, 2, 2, + ]); + }); + it("advances irrelevant frames and cutoff history, and stops at genuine gaps or lost leases", async () => { + const f = photonFixture(); + const admit = vi.fn(); + const receiver = new PhotonReceiver({ + client: f.adapter.client, + state: f.state, + lineId: "line", + intakeAfter: Date.now() + 1000, + assertOwned: guard, + admit, + failure: guard, + catchUp: () => + stream([ + photonEvent(10), + { type: "photon.ignored", sequence: 11 }, + { type: "catchup.complete", headSequence: 11 }, + ]), + }); + await receiver.catchUp(); + expect(admit).not.toHaveBeenCalled(); + expect(await f.state.read("checkpoint")).toMatchObject({ sequence: 11 }); + const gap = new PhotonReceiver({ + client: f.adapter.client, + state: f.state, + lineId: "line", + intakeAfter: 0, + assertOwned: guard, + admit, + failure: guard, + catchUp: () => + stream([ + photonEvent(13), + { type: "catchup.complete", headSequence: 13 }, + ]), + }); + await expect(gap.catchUp()).rejects.toMatchObject({ code: "history_gap" }); + const lost = new PhotonReceiver({ + client: f.adapter.client, + state: f.state, + lineId: "line", + intakeAfter: 0, + assertOwned: async () => { + throw new Error("lease lost"); + }, + admit, + failure: guard, + }); + await expect(lost.catchUp()).rejects.toThrow("lease lost"); + expect(admit).not.toHaveBeenCalled(); + }); + it("recovers sparse shared-project sequences and commits only a complete ordered replay", async () => { + const f = photonFixture(); + const admit = vi.fn(guard); + await f.state.update("checkpoint", () => ({schema:1, lineId:"line", sequence:0})); + let events: any[] = [photonEvent(1_008_648_034), {type:"catchup.complete",headSequence:1_008_648_034}]; + const receiver = new PhotonReceiver({client:f.adapter.client,state:f.state,lineId:"line",allocation:"shared",intakeAfter:0,assertOwned:guard,admit,failure:guard,catchUp:()=>stream(events)}); + await receiver.catchUp(); + expect(admit).toHaveBeenCalledTimes(1); + expect(await f.state.read("checkpoint")).toMatchObject({sequence:1_008_648_034}); + events=[photonEvent(1_008_648_090),photonEvent(1_008_648_080),{type:"catchup.complete",headSequence:1_008_648_090}]; + await expect(receiver.catchUp()).rejects.toThrow(/out of order/); + expect(await f.state.read("checkpoint")).toMatchObject({sequence:1_008_648_034}); + events=[photonEvent(1_008_648_080)]; + await expect(receiver.catchUp()).rejects.toMatchObject({code:"network"}); + expect(await f.state.read("checkpoint")).toMatchObject({sequence:1_008_648_034}); + events=[{type:"catchup.complete",headSequence:0}]; + await expect(receiver.catchUp()).rejects.toMatchObject({code:"history_gap"}); + }); + it("reads sequence-only and complete frames over authenticated synthetic gRPC", async () => { + const f = photonFixture(); + const server = new Server(); + server.addService( + { + catchUp: { + path: PHOTON_CATCHUP_PATH, + requestStream: false, + responseStream: true, + requestSerialize: (b: Buffer) => b, + requestDeserialize: (b: Buffer) => b, + responseSerialize: (b: Buffer) => b, + responseDeserialize: (b: Buffer) => b, + }, + }, + { + catchUp: (call: any) => { + expect(call.metadata.get("authorization")).toEqual([ + "Bearer private-line-token", + ]); + expect(call.request).toEqual(photonCatchUpRequest(2)); + call.write(Buffer.from([8, 3])); + call.write(Buffer.from([162, 1, 2, 8, 3])); + call.end(); + }, + }, + ); + const port = await new Promise((resolve, reject) => + server.bindAsync( + "127.0.0.1:0", + ServerCredentials.createInsecure(), + (error, port) => (error ? reject(error) : resolve(port)), + ), + ); + const transport = new PhotonRecoveryTransport( + f.authentication, + new Client(`127.0.0.1:${port}`, credentials.createInsecure()), + ); + try { + const result = []; + for await (const event of transport.catchUp(2)) result.push(event); + expect(result).toEqual([ + { type: "photon.ignored", sequence: 3 }, + { type: "catchup.complete", headSequence: 3 }, + ]); + } finally { + transport.close(); + server.forceShutdown(); + } + }); + it("keeps state company scoped and rejects corrupt cursor envelopes", async () => { + const f = photonFixture(); + await f.state.update("checkpoint", () => ({ sequence: 1 })); + expect( + await new PhotonState( + { ...f.state.scope, companyId: "other" }, + f.persistence, + ).read("checkpoint"), + ).toBeNull(); + expect(() => photonEnvelopeSequence(Buffer.from([8, 128]))).toThrow(); + expect(() => + decodePhotonRecoveryFrame(Buffer.from([8, 1, 8, 2])), + ).toThrow(); + }); +}); + +describe("Photon native prompts and media", () => { + it("binds duplicate titles and option labels only by returned IDs across restart", async () => { + const f = photonFixture(); + const binding = { + version: 1 as const, + reference: "referenceA", + interactionId: "one", + publicationId: "publication1", + sessionGeneration: 1, + expiresAt: new Date(Date.now() + 60000).toISOString(), + }; + const first = await publishPhotonPrompt({ + adapter: f.adapter, + threadId: f.threadId, + binding, + interaction: question("one"), + questionIndex: 0, + assertCurrent: guard, + }); + const second = await publishPhotonPrompt({ + adapter: f.adapter, + threadId: f.threadId, + binding: { + ...binding, + reference: "referenceB", + interactionId: "two", + publicationId: "publication2", + }, + interaction: question("two"), + questionIndex: 0, + assertCurrent: guard, + }); + expect(first.pollMessageGuid).not.toBe(second.pollMessageGuid); + expect(Object.values(first.options)).toEqual(["a", "b"]); + expect( + await f.state.read(`poll-message:${first.pollMessageGuid}`), + ).toMatchObject({ reference: "referenceA" }); + await publishPhotonPrompt({ + adapter: f.adapter, + threadId: f.threadId, + binding, + interaction: question("one"), + questionIndex: 0, + assertCurrent: guard, + }); + expect(f.client.polls.create).toHaveBeenCalledTimes(2); + }); + it("reuses poll clientMessageId after an accepted create loses its local receipt", async () => { + const f = photonFixture(); + const create = f.client.polls.create.getMockImplementation()!; + f.client.polls.create.mockImplementationOnce(async (...args) => { + await create(...args); + throw new Error("crash"); + }); + const input = { + adapter: f.adapter, + threadId: f.threadId, + binding: { + version: 1 as const, + reference: "referenceA", + interactionId: "one", + publicationId: "p", + sessionGeneration: 1, + expiresAt: new Date().toISOString(), + }, + interaction: question(), + questionIndex: 0, + assertCurrent: guard, + }; + await expect(publishPhotonPrompt(input)).rejects.toMatchObject({ + code: "delivery_unknown", + }); + await expect(publishPhotonPrompt(input)).rejects.toMatchObject({ + code: "delivery_unknown", + }); + await publishPhotonPrompt({ ...input, retryUnknown: true }); + expect(f.polls.size).toBe(1); + }); + it("validates numbered selection, custom answers, optional skips and explicit references", () => { + expect(parsePhotonQuestionAnswer(question(), 0, "2")).toEqual({ + questionId: "q1", + optionIds: ["b"], + }); + expect(() => parsePhotonQuestionAnswer(question(), 0, "yes")).toThrow(); + expect(() => parsePhotonQuestionAnswer(question(), 0, "1,2")).toThrow(); + expect(photonResponseCommand("yes")).toBeNull(); + expect( + photonResponseCommand("/answer referenceA.2 custom answer"), + ).toMatchObject({ + reference: "referenceA", + questionIndex: 1, + value: "custom answer", + }); + const multi = question(); + multi.payload.questions[0].selectionMode = "multi"; + expect(parsePhotonQuestionAnswer(multi, 0, "1,2").optionIds).toEqual([ + "a", + "b", + ]); + multi.payload.questions[0].required = false; + expect(parsePhotonQuestionAnswer(multi, 0, "skip").optionIds).toEqual([]); + }); + it("rejects foreign attachment provenance before downloading and bounds decoded images", async () => { + const f = photonFixture(); + await expect( + downloadPhotonAttachment(f.adapter.client, "line", { + kind: "photon_attachment", + lineId: "other", + chatGuid: f.chat.guid, + messageGuid: "m", + attachmentGuid: "a", + }), + ).rejects.toThrow("another line"); + await expect( + downloadPhotonAttachment(f.adapter.client, "line", { + kind: "photon_attachment", + lineId: "line", + chatGuid: "other", + messageGuid: "m", + attachmentGuid: "a", + }), + ).rejects.toThrow("does not belong"); + const wrongMessage = await f.client.messages.get("different-message"); + wrongMessage.content.attachments = [{ guid: "a" }] as typeof wrongMessage.content.attachments; + f.client.messages.get.mockResolvedValueOnce(wrongMessage); + await expect(downloadPhotonAttachment(f.adapter.client, "line", { + kind: "photon_attachment", + lineId: "line", + chatGuid: f.chat.guid, + messageGuid: "m", + attachmentGuid: "a", + })).rejects.toThrow("does not belong"); + expect(f.client.attachments.downloadStream).not.toHaveBeenCalled(); + const png = await sharp({ + create: { width: 2, height: 2, channels: 3, background: "white" }, + }) + .png() + .toBuffer(); + await validatePhotonImage(png, "image/png"); + await expect(validatePhotonImage(png, "image/jpeg")).rejects.toThrow( + "declared", + ); + expect(() => validateHeifDimensions(png)).toThrow(); + }); + it("preserves numerical free-text answers for canonical numeric validation", () => { + const interaction = question(); + interaction.payload.questions[0].allowOther = true; + interaction.payload.questions[0].options = [ + { id: "number", label: "Enter a number", freeText: true }, + ]; + expect(parsePhotonQuestionAnswer(interaction, 0, "42")).toEqual({ + questionId: "q1", + optionIds: ["number"], + otherText: "42", + }); + }); + it("downloads a shared project alias with a native header UUID only after source ownership is verified", async () => { + const f = photonFixture(); + const body = Buffer.from("synthetic shared attachment"); + const alias = "spc-att-11111111-1111-4111-8111-111111111111"; + const message = photonEvent(1).message; + const info = {guid: alias, totalBytes: body.length, fileName: "test.txt", mimeType: "text/plain", isHidden: false, isSticker: false}; + (message.content.attachments as any[]).push(info); + f.client.messages.get.mockResolvedValue(message); + const native = {...info, guid: "22222222-2222-4222-8222-222222222222"}; + f.client.attachments.downloadStream.mockImplementation(() => stream([ + {type: "header", info: native}, {type: "primaryChunk", data: body}, + ])); + const locator = {kind: "photon_attachment" as const, lineId: "line", chatGuid: f.chat.guid, messageGuid: message.guid, attachmentGuid: alias}; + await expect(downloadPhotonAttachment(f.adapter.client, "line", locator)).rejects.toThrow("metadata changed"); + await expect(downloadPhotonAttachment(f.adapter.client, "line", locator, "shared")).resolves.toEqual(body); + expect(f.client.attachments.downloadStream).toHaveBeenLastCalledWith(alias); + native.mimeType = "application/octet-stream"; + await expect(downloadPhotonAttachment(f.adapter.client, "line", locator, "shared")).rejects.toThrow("metadata changed"); + f.client.attachments.downloadStream.mockClear(); + await expect(downloadPhotonAttachment(f.adapter.client, "line", {...locator, chatGuid: "wrong-chat"}, "shared")).rejects.toThrow("does not belong"); + expect(f.client.attachments.downloadStream).not.toHaveBeenCalled(); + }); + it("retains source-bound Live Photo companion bytes and waits for incomplete companions", async () => { + const f = photonFixture(); + const photo = Buffer.from("primary"), + video = Buffer.from("companion"); + const message = photonEvent(1).message; + (message.content.attachments as any[]).push({ + guid: "live-photo", + totalBytes: photo.length, + isHidden: false, + isSticker: false, + }); + f.client.messages.get.mockResolvedValue(message); + const locator = { + kind: "photon_attachment" as const, + lineId: "line", + chatGuid: f.chat.guid, + messageGuid: message.guid, + attachmentGuid: "live-photo", + }; + const header = { + type: "header", + info: { guid: "live-photo", totalBytes: photo.length }, + companionInfo: { + kind: "live-photo-video", + mimeType: "video/quicktime", + fileName: "photo.mov", + totalBytes: video.length, + }, + }; + f.client.attachments.downloadStream.mockImplementationOnce(() => + stream([header, { type: "primaryChunk", data: photo }]), + ); + await expect( + downloadPhotonAttachment(f.adapter.client, "line", locator), + ).rejects.toMatchObject({ code: "attachment_not_ready" }); + f.client.attachments.downloadStream.mockImplementationOnce(() => + stream([ + header, + { type: "primaryChunk", data: photo }, + { type: "companionChunk", data: video }, + ]), + ); + const result = await downloadPhotonAttachment( + f.adapter.client, + "line", + locator, + ); + expect(result).toEqual(photo); + expect(takePhotonCompanion(result)).toEqual({ + unavailable: false, + fileName: "photo.mov", + mimeType: "video/quicktime", + data: video, + }); + expect(takePhotonCompanion(result)).toBeUndefined(); + }); + it("converts a real synthetic HEIC with the installed native binary and bounds its dimensions", async () => { + const heic = await readFile( + new URL("./fixtures/synthetic.heic", import.meta.url), + ); + const jpeg = await photonHeifPreview(heic); + expect(await sharp(jpeg).metadata()).toMatchObject({ + format: "jpeg", + width: 16, + height: 16, + }); + const oversized = Buffer.from(heic); + const at = oversized.indexOf("ispe"); + expect(at).toBeGreaterThan(0); + oversized.writeUInt32BE(100_000, at + 8); + expect(() => validateHeifDimensions(oversized)).toThrow("pixel limit"); + }); +}); diff --git a/server/src/__tests__/plugin-install-autobuild.test.ts b/server/src/__tests__/plugin-install-autobuild.test.ts index 158bef7aaa..d9d3a59961 100644 --- a/server/src/__tests__/plugin-install-autobuild.test.ts +++ b/server/src/__tests__/plugin-install-autobuild.test.ts @@ -213,12 +213,18 @@ describe("ensureLocalPluginBuilt", () => { expect(execStub).not.toHaveBeenCalled(); }); - it("bootstraps standalone bundled plugins before building them", async () => { + it.each([ + undefined, + "allowBuilds:\n protobufjs: false\n", + "packages:\n - ../untrusted\ndangerouslyAllowAllBuilds: true\n", + ])("bootstraps standalone plugins without trusting workspace policy: %s", async (localPolicy) => { const fixture = await createBundledPluginFixture("standalone", { rootDir: standaloneRepoPluginRoot }); cleanupPaths.add(fixture.packageRoot); + if (localPolicy) await writeFile(path.join(fixture.packageRoot, "pnpm-workspace.yaml"), localPolicy); + const installArgs = ["install", "--ignore-workspace", ...(localPolicy ? ["--ignore-scripts"] : []), "--no-lockfile"]; const execStub = vi.fn(async (_file: string, args: readonly string[]) => { - if (args.join(" ") === "install --ignore-workspace --no-lockfile") { + if (args.join(" ") === installArgs.join(" ")) { await mkdir(path.join(fixture.packageRoot, "node_modules", "@paperclipai", "plugin-sdk"), { recursive: true }); } if (args.join(" ") === "build") { @@ -239,7 +245,7 @@ describe("ensureLocalPluginBuilt", () => { expect(execStub).toHaveBeenNthCalledWith( 1, "pnpm", - ["install", "--ignore-workspace", "--no-lockfile"], + installArgs, { cwd: fixture.packageRoot, timeout: 120_000 }, ); expect(execStub).toHaveBeenNthCalledWith( diff --git a/server/src/__tests__/run-identity.test.ts b/server/src/__tests__/run-identity.test.ts index 634d6a70cc..9155a9b9fa 100644 --- a/server/src/__tests__/run-identity.test.ts +++ b/server/src/__tests__/run-identity.test.ts @@ -1,7 +1,7 @@ import { randomUUID } from "node:crypto"; import { eq, sql } from "drizzle-orm"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; -import { agents, companies, createDb, heartbeatRuns, heartbeatRunEvents, issueComments, issueThreadInteractions, issues } from "@paperclipai/db"; +import { agentWakeupRequests, agents, companies, createDb, heartbeatRuns, heartbeatRunEvents, issueComments, issueThreadInteractions, issues } from "@paperclipai/db"; import { getEmbeddedPostgresTestSupport, startEmbeddedPostgresTestDatabase } from "./helpers/embedded-postgres.js"; import { acceptSteeredIdentity, captureRunIdentity, initializeRunIdentity, listRunIdentityContexts, rejectSteeredIdentity, reserveSteeredIdentity } from "../services/run-identity.js"; @@ -41,6 +41,71 @@ const support = await getEmbeddedPostgresTestSupport(); await initializeRunIdentity(db, { ...input, responsibleUserId: "B", cause: "restart" }); expect(await listRunIdentityContexts(db, input.companyId, input.runId)).toHaveLength(4); }); + async function seedInterrupt() { + const input = await seed(); + const queueId = randomUUID(), wakeupRequestId = randomUUID(); + const contextSnapshot = { issueId: input.issueId, wakeCommentIds: input.messageIds }; + await db.insert(agentWakeupRequests).values([ + { id: queueId, companyId: input.companyId, agentId: input.agentId, source: "automation", + status: "coalesced", runId: input.runId, requestedByActorType: "system", + payload: { issueId: input.issueId, _paperclipWakeContext: { wakeCommentIds: input.messageIds }, + queuedCommentInterrupt: { actorId: "operator", requestedAt: new Date().toISOString() } } }, + { id: wakeupRequestId, companyId: input.companyId, agentId: input.agentId, source: "on_demand", + status: "queued", runId: input.runId, requestedByActorType: "user", requestedByActorId: "operator", + idempotencyKey: `queued-comment-interrupt:${queueId}` }, + ]); + await db.update(heartbeatRuns).set({ wakeupRequestId, contextSnapshot }).where(eq(heartbeatRuns.id, input.runId)); + return { ...input, queueId, wakeupRequestId, contextSnapshot }; + } + + it("uses the clicking operator through startup and restart without changing message authors", async () => { + const input = await seedInterrupt(); + // A stale originating context cannot replace the explicit click's identity. + const identity = await initializeRunIdentity(db, { + ...input, responsibleUserId: "A", parentContextId: randomUUID(), cause: "dispatch", + }); + expect(identity).toMatchObject({ responsibleUserId: "operator", cause: "queued_comment_interrupt" }); + const history = await listRunIdentityContexts(db, input.companyId, input.runId); + expect(history.map(row => row.responsibleUserId)).toEqual(["operator", "operator", "operator", "operator"]); + expect(await initializeRunIdentity(db, { ...input, responsibleUserId: "B", cause: "restart" })).toEqual(identity); + const comments = await db.select().from(issueComments).where(eq(issueComments.issueId, input.issueId)); + expect(input.messageIds.map(id => comments.find(c => c.id === id)?.authorUserId)).toEqual(["A", "B", "A"]); + expect((await captureRunIdentity(db, input)).context?.responsibleUserId).toBe("operator"); + const retryRunId = randomUUID(); + await db.insert(heartbeatRuns).values({ id: retryRunId, companyId: input.companyId, + agentId: input.agentId, contextSnapshot: input.contextSnapshot, status: "queued", retryOfRunId: input.runId }); + const retried = await initializeRunIdentity(db, { companyId: input.companyId, runId: retryRunId, + issueId: input.issueId, parentRunId: input.runId, responsibleUserId: "A", cause: "retry" }); + expect(retried.responsibleUserId).toBe("operator"); + }); + + it.each(["malformed", "missing", "unconsumed", "other-run", "other-task", "other-agent", "other-actor", "other-message"])( + "rejects %s interrupt authority before creating any execution identity", async (fault) => { + const input = await seedInterrupt(); + if (fault === "malformed" || fault === "missing") { + await db.update(agentWakeupRequests).set({ + idempotencyKey: `queued-comment-interrupt:${fault === "malformed" ? "not-an-id" : randomUUID()}`, + }).where(eq(agentWakeupRequests.id, input.wakeupRequestId)); + } else if (fault === "other-actor") { + await db.update(agentWakeupRequests).set({ requestedByActorId: "someone-else" }).where(eq(agentWakeupRequests.id, input.wakeupRequestId)); + } else { + const [receipt] = await db.select().from(agentWakeupRequests).where(eq(agentWakeupRequests.id, input.queueId)); + if (fault === "other-agent") { + const agentId = randomUUID(); + await db.insert(agents).values({ id: agentId, companyId: input.companyId, name: "Other", role: "engineer" }); + await db.update(agentWakeupRequests).set({ agentId }).where(eq(agentWakeupRequests.id, input.queueId)); + } else await db.update(agentWakeupRequests).set( + fault === "unconsumed" ? { status: "deferred_issue_execution" } : + fault === "other-run" ? { runId: null } : + { payload: { ...receipt.payload, ...(fault === "other-task" ? { issueId: randomUUID() } : + { _paperclipWakeContext: { wakeCommentIds: [randomUUID()] } }) } }, + ).where(eq(agentWakeupRequests.id, input.queueId)); + } + await expect(initializeRunIdentity(db, { ...input, responsibleUserId: "A", cause: "dispatch" })).rejects.toThrow("interrupt authority"); + expect(await listRunIdentityContexts(db, input.companyId, input.runId)).toHaveLength(0); + }, + ); + it("holds acquisition during uncertain steering, preserves snapshots, and never rewinds on replay", async () => { const input = await seed(); await initializeRunIdentity(db, { ...input, messageIds: [input.messageIds[0]], responsibleUserId: "A", cause: "instruction" }); diff --git a/server/src/__tests__/task-search-quality.test.ts b/server/src/__tests__/task-search-quality.test.ts new file mode 100644 index 0000000000..de84c1de66 --- /dev/null +++ b/server/src/__tests__/task-search-quality.test.ts @@ -0,0 +1,147 @@ +import { randomUUID } from "node:crypto"; +import { performance } from "node:perf_hooks"; +import { writeFile } from "node:fs/promises"; +import { cpus, platform, release, totalmem } from "node:os"; +import { sql } from "drizzle-orm"; +import { afterAll, beforeAll, describe, expect, it } from "vitest"; +import { companies, createDb, documents, issueComments, issueDocuments, issues, getEmbeddedPostgresTestSupport, startEmbeddedPostgresTestDatabase } from "@paperclipai/db"; +import { companySearchQuerySchema } from "@paperclipai/shared"; +import { companySearchService } from "../services/company-search.js"; +import { parseTaskSearch, taskSearchCtes, taskSearchScore } from "../services/task-search.js"; +import { issueService } from "../services/issues.js"; +import { searchQualityMetrics, taskSearchCases, taskSearchCorpus } from "./fixtures/task-search-corpus.js"; + +const support = await getEmbeddedPostgresTestSupport(); +const baseline = process.env.SEARCH_EVAL_BASELINE === "1"; + +describe.skipIf(!support.supported)("task search relevance rubric (real PostgreSQL)", () => { + let tempDb: Awaited>; + let db: ReturnType; + const companyId = randomUUID(); + const keys = new Map(); + const plans: Record = {}; + const latency: Array<{ engine: string; q: string; firstMs: number; p95Ms: number; warmMs: number[] }> = []; + let postgresVersion = ""; + const report: Array<{ engine: string; name: string; q: string; keys: string[]; ndcg5: number; reciprocalRank: number; ms: number }> = []; + + beforeAll(async () => { + tempDb = await startEmbeddedPostgresTestDatabase("paperclip-search-quality-"); + db = createDb(tempDb.connectionString); + postgresVersion = String((await db.execute(sql`SELECT version()`))[0]!.version); + await db.insert(companies).values({ id: companyId, name: "Search benchmark", issuePrefix: "EVAL" }); + for (const [index, entry] of taskSearchCorpus.entries()) { + const id = randomUUID(); + keys.set(id, entry.key); + const updatedAt = new Date(Date.UTC(2026, 0, 1) + index * 60_000); + await db.insert(issues).values({ id, companyId, title: entry.title, + identifier: "identifier" in entry ? entry.identifier : `EVAL-${index + 1}`, + description: "description" in entry ? entry.description : null, + status: "status" in entry ? entry.status : "todo", updatedAt, createdAt: updatedAt }); + if ("comments" in entry) { + for (const body of entry.comments) await db.insert(issueComments).values({ companyId, issueId: id, body, updatedAt }); + } + if ("document" in entry) { + const documentId = randomUUID(); + await db.insert(documents).values({ id: documentId, companyId, title: entry.document.title, latestBody: entry.document.body, format: "markdown" }); + await db.insert(issueDocuments).values({ companyId, issueId: id, documentId, key: "plan" }); + } + } + }); + + afterAll(async () => { + const summary = ["full", "quick"].map((engine) => { + const rows = report.filter((row) => row.engine === engine); + const known = rows.filter((row) => Object.keys(taskSearchCases.find((item) => item.name === row.name)!.relevant).length > 0); + return { engine, queries: rows.length, ndcg5: rows.reduce((sum, row) => sum + row.ndcg5, 0) / rows.length, + mrr: known.reduce((sum, row) => sum + row.reciprocalRank, 0) / known.length }; + }); + console.log("SEARCH QUALITY", JSON.stringify(summary)); + if (process.env.SEARCH_EVAL_REPORT) await writeFile(process.env.SEARCH_EVAL_REPORT, JSON.stringify({ + environment: { postgresVersion, platform: platform(), release: release(), cpu: cpus()[0]?.model, memoryBytes: totalmem(), warmSamples: 20 }, + summary, queries: report, latency, plans, + }, null, 2)); + await tempDb?.cleanup(); + }); + + for (const engine of ["full", "quick"] as const) { + for (const testCase of taskSearchCases) { + it(`${engine}: ${testCase.name}`, async () => { + const start = performance.now(); + const rows = engine === "full" + ? (await companySearchService(db).search(companyId, companySearchQuerySchema.parse({ q: testCase.q }))).results.filter((row) => row.type === "issue") + : await issueService(db).list(companyId, { q: testCase.q, limit: 50 }); + const resultKeys = rows.map((row) => keys.get(row.id)!); + report.push({ engine, name: testCase.name, q: testCase.q, keys: resultKeys, ...searchQualityMetrics(resultKeys, testCase.relevant), ms: performance.now() - start }); + if (baseline) return; + if (testCase.first) expect(resultKeys[0], JSON.stringify(resultKeys)).toBe(testCase.first); + for (const absent of testCase.absent ?? []) expect(resultKeys).not.toContain(absent); + for (const [key, grade] of Object.entries(testCase.relevant)) if (grade === 3) expect(resultKeys.slice(0, 5)).toContain(key); + if (Object.keys(testCase.relevant).length === 0) expect(resultKeys).toEqual([]); + }); + } + } + it("meets the aggregate relevance gates", () => { + if (baseline) return; + for (const engine of ["full", "quick"]) { + const rows = report.filter((row) => row.engine === engine); + const known = rows.filter((row) => taskSearchCases.find((entry) => entry.name === row.name)!.q !== "quasarxylophone"); + expect(known.reduce((sum, row) => sum + row.reciprocalRank, 0) / known.length).toBeGreaterThanOrEqual(0.95); + expect(rows.reduce((sum, row) => sum + row.ndcg5, 0) / rows.length).toBeGreaterThanOrEqual(0.90); + } + }); + + it("handles empty quotes, literal punctuation, and oversized title words without errors", async () => { + if (baseline) return; + await db.insert(issues).values({ companyId, title: "x".repeat(300) }); + for (const q of ['""', "%", "_", "\\", "z".repeat(200)]) { + const full = await companySearchService(db).search(companyId, companySearchQuerySchema.parse({ q })); + const quick = await issueService(db).list(companyId, { q, limit: 50 }); + if (q === '""' || q.startsWith("z")) { + expect(full.results).toEqual([]); + expect(quick).toEqual([]); + } else { + for (const row of quick) expect(row.title).toContain(q); + } + } + }); + + it.runIf(process.env.SEARCH_EVAL_SCALE === "1")("measures 10k tasks / 30k comments", async () => { + await db.execute(sql` + INSERT INTO issues (company_id, title, description, identifier) + SELECT ${companyId}, 'Routine deployment checkpoint ' || n, + repeat('Review the build output and update the deployment checklist. ', 5), 'SCALE-' || n + FROM generate_series(1, 10000) n + `); + await db.execute(sql` + INSERT INTO issue_comments (company_id, issue_id, body) + SELECT ${companyId}, id, repeat('Routine progress report: verified the output and recorded the findings. ', 5) + FROM issues CROSS JOIN generate_series(1, 3) n + WHERE company_id = ${companyId} AND identifier LIKE 'SCALE-%' + `); + await db.execute(sql`ANALYZE issues`); + await db.execute(sql`ANALYZE issue_comments`); + for (const engine of ["full", "quick"] as const) { + for (const q of ["GitHub OAuth", "OAuth callback GitHub", "mibile api", "search", "quasarxylophone", "routine"]) { + const durations: number[] = []; + for (let i = 0; i < 21; i++) { + const start = performance.now(); + if (engine === "full") await companySearchService(db).search(companyId, companySearchQuerySchema.parse({ q })); + else await issueService(db).list(companyId, { q, limit: 20 }); + durations.push(performance.now() - start); + } + const warmMs = durations.slice(1); + latency.push({ engine, q, firstMs: durations[0]!, p95Ms: [...warmMs].sort((a, b) => a - b)[18]!, warmMs }); + } + } + for (const q of ["GitHub OAuth", "mibile api", "routine"]) { + const search = parseTaskSearch(q); + if (!baseline) plans[q] = await db.execute(sql` + EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) + ${taskSearchCtes(companyId, search)} + SELECT m.id, ${taskSearchScore(search)} AS score FROM matched m ORDER BY score DESC LIMIT 20 + `); + } + console.log("SEARCH LATENCY", JSON.stringify(latency.map(({ warmMs: _warmMs, ...row }) => row))); + }, 120_000); + +}); diff --git a/server/src/__tests__/task-search-query.test.ts b/server/src/__tests__/task-search-query.test.ts new file mode 100644 index 0000000000..4bc93dfa97 --- /dev/null +++ b/server/src/__tests__/task-search-query.test.ts @@ -0,0 +1,19 @@ +import { describe, expect, it } from "vitest"; +import { parseTaskSearch } from "../services/task-search.js"; + +describe("task search query intent", () => { + it("keeps negation, short domain terms and quoted filler", () => { + expect(parseTaskSearch('the API is not "in the UI"').tokens).toEqual(["api", "is", "not", "in the ui"]); + expect(parseTaskSearch("the and").tokens).toEqual(["the", "and"]); + }); + + it("retains the strictest intent for repeated terms", () => { + expect(parseTaskSearch('callback "callback"').terms).toEqual([{ text: "callback", quoted: true }]); + }); + + it("normalizes task identifiers without guessing their numbers", () => { + for (const q of ["PAP-42", "pap42", "PAP 42"]) expect(parseTaskSearch(q).identifierQuery).toBe("pap-42"); + expect(parseTaskSearch("T123-42").identifierQuery).toBe("t123-42"); + expect(parseTaskSearch("PAP-420").identifierQuery).toBe("pap-420"); + }); +}); diff --git a/server/src/__tests__/tool-access-service.test.ts b/server/src/__tests__/tool-access-service.test.ts index 8f432c4f9f..2b961cb3d1 100644 --- a/server/src/__tests__/tool-access-service.test.ts +++ b/server/src/__tests__/tool-access-service.test.ts @@ -4974,6 +4974,7 @@ describeEmbeddedPostgres("tool access service", () => { expect(res.body.apps.map((app: { slug: string }) => app.slug)).toEqual( expect.arrayContaining([ "agentmail", + "imessage-photon", "jira", "airtable", "asana", @@ -4994,7 +4995,7 @@ describeEmbeddedPostgres("tool access service", () => { "github", ]), ); - expect(res.body.apps).toHaveLength(41); + expect(res.body.apps).toHaveLength(46); expect( res.body.apps.find((app: { slug: string }) => app.slug === "gmail") .ownershipAvailability, diff --git a/server/src/app.ts b/server/src/app.ts index 1681a82576..6bb656c3b2 100644 --- a/server/src/app.ts +++ b/server/src/app.ts @@ -1,3 +1,4 @@ +import { aiConnectionRoutes } from "./routes/ai-connections.js"; import { projectToolRoutes } from "./routes/project-tools.js"; import { emailChannelService } from "./services/email-channels.js"; import { emailRoutes, emailWebhookRoutes } from "./routes/email.js"; @@ -826,6 +827,7 @@ export async function createApp( app.locals.toolGateway = toolGateway; app.locals.toolActionDeliveries = toolActionDeliveries; app.use(mcpGatewayProtocolRoutes(toolGateway)); + api.use(aiConnectionRoutes(db, { deploymentMode: opts.deploymentMode, deploymentExposure: opts.deploymentExposure, trustedLocalStdioRuntimeHost })); api.use( toolAccessRoutes(db, { deploymentMode: opts.deploymentMode, diff --git a/server/src/attachment-types.ts b/server/src/attachment-types.ts index 1650ef87bd..627ce99b14 100644 --- a/server/src/attachment-types.ts +++ b/server/src/attachment-types.ts @@ -20,6 +20,10 @@ export const DEFAULT_ALLOWED_TYPES: readonly string[] = [ "image/jpg", "image/webp", "image/gif", + "image/heic", + "image/heif", + "image/heic-sequence", + "image/heif-sequence", "audio/mpeg", "audio/mp4", "audio/ogg", diff --git a/server/src/index.ts b/server/src/index.ts index d4068563c4..19a8896c9d 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -94,6 +94,7 @@ import { createProductionLoginSessionReaperRuntime, } from "./services/device-login-reaper.js"; import { createProductionSetupTokenReaper } from "./services/setup-token-reaper.js"; +import { localAiLoginService } from "./services/local-ai-login.js"; import { resolveWorktreeRunExecutionActivationState } from "./services/instance-settings.js"; import { parseAdapterRegistryEnv, @@ -1152,6 +1153,7 @@ async function startServerWithDatabaseTeardown( ["reconciliation_delivery", () => heartbeat ? deliverReconciledExecutions(db, heartbeat.wakeup) : undefined], ["status_delivery", () => deliverExecutionStatuses(db)], ["automatic_disposition", () => settleUnrecoverableExecutions(db)], + ["local_ai_login_cleanup", () => localAiLoginService(db).reapExpired()], ] as const; const sweepExecutionControl = () => { if (heartbeatSchedulerStopped) return; diff --git a/server/src/modules/wake-queue/adapters/postgres.test.ts b/server/src/modules/wake-queue/adapters/postgres.test.ts index 9e2567f935..8a5a2b1951 100644 --- a/server/src/modules/wake-queue/adapters/postgres.test.ts +++ b/server/src/modules/wake-queue/adapters/postgres.test.ts @@ -242,6 +242,24 @@ describeEmbeddedPostgres("wake-queue postgres adapter", () => { }); } + it.each(["queued", "running", "scheduled_retry"])("does not promote another turn behind a %s successor without an execution lock", async (status) => { + const companyId = await seedCompany(); + const agentId = await seedAgent({ companyId }); + const issueId = await seedIssue({ companyId, assigneeAgentId: agentId }); + const runId = await seedRun({ companyId, agentId, status: "succeeded", contextSnapshot: { issueId } }); + await seedRun({ companyId, agentId, status, contextSnapshot: { issueId } }); + const wakeId = await seedDeferredWake({ companyId, agentId, issueId }); + const adapter = createPostgresWakeQueueAdapter(db, stubDeps); + let drained = false; + await adapter.withIssueExecutionLock({ companyId, runId, now: new Date() }, async () => { + drained = true; + return { outcome: { kind: "released" }, postCommitEffects: [] }; + }); + expect(drained).toBe(false); + const [wake] = await db.select().from(agentWakeupRequests).where(eq(agentWakeupRequests.id, wakeId)); + expect(wake.status).toBe("deferred_issue_execution"); + }); + it("leaves deferred work untouched until the effective execution hold clears", async () => { const companyId = await seedCompany(); const agentId = await seedAgent({ companyId }); diff --git a/server/src/modules/wake-queue/adapters/postgres.ts b/server/src/modules/wake-queue/adapters/postgres.ts index 514ddcdcf2..efbcec9e1e 100644 --- a/server/src/modules/wake-queue/adapters/postgres.ts +++ b/server/src/modules/wake-queue/adapters/postgres.ts @@ -894,6 +894,10 @@ export function createWakeAdmissionWriter(): WakeAdmissionWriter { .set({ payload: input.mergedPayload, coalescedCount: input.nextCoalescedCount, + ...(input.manualUserWakeActorId ? { + requestedByActorType: "user", + requestedByActorId: input.manualUserWakeActorId, + } : {}), updatedAt: new Date(), }) .where( @@ -1052,6 +1056,20 @@ export function createPostgresWakeQueueAdapter(db: Db, deps: WakeQueuePostgresAd throw new Error(`wake-queue: pre-drain decision ${preDrain.kind} reached without an issue row`); } + // Enqueue does not stamp executionRunId until dispatch. A concurrent + // queued successor still owns the next turn, including during a late + // finalization/stranded-queue retry under this issue lock. Another + // agent's review participation retains its separate recovery path. + const [successor] = await tx.select({ id: heartbeatRuns.id }).from(heartbeatRuns).where(and( + eq(heartbeatRuns.companyId, input.companyId), + eq(heartbeatRuns.agentId, run.agentId), + sql`${heartbeatRuns.id} <> ${run.id}`, + or(eq(heartbeatRuns.nativeIssueId, issueRow.id), + sql`${heartbeatRuns.contextSnapshot}->>'issueId' = ${issueRow.id}`), + inArray(heartbeatRuns.status, ["queued", "running", "scheduled_retry"]), + )).limit(1); + if (successor) return { outcome: { kind: "released" }, postCommitEffects: [], run: runSnapshot }; + if (preDrain.kind === "blocked") { return { outcome: { diff --git a/server/src/modules/wake-queue/application/ports.ts b/server/src/modules/wake-queue/application/ports.ts index 0dfc4e8f25..c4e4cc0f9e 100644 --- a/server/src/modules/wake-queue/application/ports.ts +++ b/server/src/modules/wake-queue/application/ports.ts @@ -428,6 +428,8 @@ export interface WakeAdmissionWriter { existingDeferredWakeId: string; mergedPayload: Record; nextCoalescedCount: number; + /** A fresh manual click replaces the merged queue's execution requester. */ + manualUserWakeActorId?: string; /** Persist each durable input's own receipt atomically with the merge. */ coalescedReceipt?: CoalescedDeferredAdmissionReceipt; }, diff --git a/server/src/modules/wake-queue/application/use-cases.test.ts b/server/src/modules/wake-queue/application/use-cases.test.ts index 71de306851..528acc5391 100644 --- a/server/src/modules/wake-queue/application/use-cases.test.ts +++ b/server/src/modules/wake-queue/application/use-cases.test.ts @@ -848,6 +848,17 @@ describe("admitWakeBehindIssueExecution", () => { expect(writer.coalesceIntoActiveExecutionRun).not.toHaveBeenCalled(); }); + it("gives manual input its own run boundary even when the active receipt has the same requester", async () => { + const writer = createFakeAdmissionWriter(); + const admit = createAdmitWakeBehindIssueExecution({ + reader: createFakeAdmissionReader(), writer, helpers: createFakeAdmissionHelpers(), + }); + expect(await admit(SCOPE, admissionInput({ payload: { issueId: "issue-1", manualUserWake: true } }))) + .toEqual({ kind: "deferred" }); + expect(writer.coalesceIntoActiveExecutionRun).not.toHaveBeenCalled(); + expect(writer.insertNewDeferredWake).toHaveBeenCalledTimes(1); + }); + it("keeps ordinary non-durable coalescing independent of durable actor lookup", async () => { const writer = createFakeAdmissionWriter(); const reader = createFakeAdmissionReader({ diff --git a/server/src/modules/wake-queue/application/use-cases.ts b/server/src/modules/wake-queue/application/use-cases.ts index b9817e53df..c9c35fee7d 100644 --- a/server/src/modules/wake-queue/application/use-cases.ts +++ b/server/src/modules/wake-queue/application/use-cases.ts @@ -342,6 +342,7 @@ async function promoteDeferredWake( const promotedTriggerDetail = workingCandidate.triggerDetail ?? null; const promotedPayload = { ...workingCandidate.payload }; delete promotedPayload["_paperclipWakeContext"]; + delete promotedPayload["queuedCommentInterrupt"]; const promotedContextSeed: Record = { ...workingCandidate.deferredContextSeed }; if (pauseHold.activePauseHold) { @@ -716,6 +717,12 @@ export function createAdmitWakeBehindIssueExecution(deps: { scope: TransactionScope, input: AdmitWakeBehindIssueExecutionInput, ): Promise { + const manualUserWakeActorId = input.payload?.manualUserWake === true + ? readNonEmptyString(input.requestedByActorId) : null; + if (input.payload?.manualUserWake === true && + (input.requestedByActorType !== "user" || !manualUserWakeActorId)) { + throw new Error("wake-queue: manual wake requires an authenticated user"); + } const isSameExecutionAgent = await deps.reader.isSameExecutionAgent(scope, { companyId: input.companyId, activeExecutionRunAgentId: input.activeExecutionRun.agentId, @@ -723,8 +730,10 @@ export function createAdmitWakeBehindIssueExecution(deps: { agentNameKey: input.agentNameKey, }); + // A manual click establishes a fresh execution identity. Even a matching + // requester can have a different originating identity on an exact retry. const shouldDeferFollowupWake = - deps.helpers.shouldDeferFollowupWakeForSameIssue({ + Boolean(manualUserWakeActorId) || deps.helpers.shouldDeferFollowupWakeForSameIssue({ activeRunStatus: input.activeExecutionRun.status, isSameExecutionAgent, wakeCommentId: input.wakeCommentId, @@ -830,6 +839,7 @@ export function createAdmitWakeBehindIssueExecution(deps: { existingDeferredWakeId: existingDeferred.id, mergedPayload, nextCoalescedCount: (existingDeferred.coalescedCount ?? 0) + 1, + ...(manualUserWakeActorId ? { manualUserWakeActorId } : {}), ...(input.durableReceipt ? { coalescedReceipt: { diff --git a/server/src/routes/agents.ts b/server/src/routes/agents.ts index 0306cd131a..1428fae7b8 100644 --- a/server/src/routes/agents.ts +++ b/server/src/routes/agents.ts @@ -1,3 +1,10 @@ +import { listOpenRouterModels } from "../services/openrouter-models.js"; +import { prepareManagedAiRuntime, assertManagedAiProjectAuth, stripAiAuthBindings } from "../services/ai-connection-runtime.js"; +import { ADAPTER_AUTH_MISSING_CHECK_CODE, aiConnectionBindingSchema, type AiConnectionBinding } from "@paperclipai/shared"; +import { toolConnections } from "@paperclipai/db"; +import { aiConnectionService } from "../services/ai-connections.js"; +import { assertAiConnectionCreateAccess, canInstallSharedAiConnectionForNewAgent, responsibleUserForAiRequest } from "./ai-connections.js"; +import { isAiConnectionCompatible } from "@paperclipai/shared"; import { applyConnectorSkills, resolveConnectorAssignments, annotateConnectorSkills, isConnectorSkill } from "../services/connector-runtime.js"; import { getExecutionBlocker } from "../services/execution-blocker.js"; import { paperclipRunnerTransitionConfig, normalizeLegacyRunnerProvider, isPaperclipRunnerProvider } from "@paperclipai/adapter-utils"; @@ -679,7 +686,10 @@ export function agentRoutes( factory: setupTokenLoginFactory, leases: guardedSetupTokenLeaseManager, store: setupTokenCleanupStore, - completeCredential: setupTokenSecretWriter, + completeCredential: async (input) => { + if (!input.scope.aiConnection) return setupTokenSecretWriter(input); + await aiConnectionService(db).save(input.scope.companyId, input.scope.ownerUserId, input.scope.aiConnection, input.token, input.sessionId); + }, rateLimiter: setupTokenRateLimiter, }); @@ -786,6 +796,15 @@ export function agentRoutes( // bind its own secret while the first login's later, unrelated // secret-write failure removes the directory both logins now share. async promote(authBytes, context) { + const managedSession = await adapterLoginStore.get(context.sessionId); + if (managedSession?.aiConnection) { + await adapterLoginStore.withCompanyAdapterPromotionLock(context.companyId, context.startedByUserId, context.adapterType, async () => { + if (!(await checkStagedCredentialReadiness(authBytes)).ready) throw new Error("Provider credential is not ready"); + await aiConnectionService(db).save(context.companyId, context.startedByUserId, managedSession.aiConnection!, authBytes.toString("utf8"), context.sessionId); + }); + return; + } + return withCodexAccountHomePromotionLock(undefined, context.companyId, async () => { // Hold the promotion critical-section lock across the ownership check // and the credential write. The reaper takes the same lock before it @@ -1022,6 +1041,15 @@ export function agentRoutes( }, grok_local: { async promote(authBytes, context) { + const managedSession = await adapterLoginStore.get(context.sessionId); + if (managedSession?.aiConnection) { + await adapterLoginStore.withCompanyAdapterPromotionLock(context.companyId, context.startedByUserId, context.adapterType, async () => { + if (!(await checkStagedGrokCredentialReadiness(authBytes)).ready) throw new Error("Provider credential is not ready"); + await aiConnectionService(db).save(context.companyId, context.startedByUserId, managedSession.aiConnection!, authBytes.toString("utf8"), context.sessionId); + }); + return; + } + // The same promotion critical-section lock as the Codex entry above, // keyed by the same `(companyId, startedByUserId, adapterType)` tuple, // so a Grok reclaim and a Grok write never interleave. @@ -1707,6 +1735,17 @@ export function agentRoutes( throw forbidden(decision.explanation, authorizationDeniedDetails(decision)); } + async function assertBoardCanWakeAgent(req: Request, agent: { id: string; companyId: string }) { + assertBoard(req); + if (!hasCompanyAccess(req, agent.companyId)) throw notFound("Agent not found"); + assertCompanyAccess(req, agent.companyId); + const decision = await access.decide({ + actor: req.actor, action: "agent:wake", + resource: { type: "agent", companyId: agent.companyId, agentId: agent.id }, + }); + if (!decision.allowed) throw forbidden(decision.explanation, authorizationDeniedDetails(decision)); + } + // The single owner-authorization helper for the three adapter login routes. It // requires a board actor, company access, and the same configuration // permission as the adapter Test route (`agents:create`). It returns the @@ -3071,6 +3110,10 @@ export function agentRoutes( return; } const provider = asNonEmptyString(req.query.provider); + if (type === "opencode_local" && provider === "openrouter") { + res.json(await listOpenRouterModels(refresh)); + return; + } if (type === "paperclip_runner" && provider && !isPaperclipRunnerProvider(provider)) { throw unprocessable("Unknown Paperclip Runner provider"); } @@ -3131,6 +3174,42 @@ export function agentRoutes( }); } + async function testManagedEnvironment(adapterType: string, context: Parameters["testEnvironment"]>[0], binding: AiConnectionBinding) { + await assertManagedAiProjectAuth(context.config, binding.provider, context.executionTarget); + const result = await requireServerAdapter(adapterType).testEnvironment(context); + if (result.status === "fail") return result; + if (!result.checks.some(check => check.code.includes("hello_probe"))) { + const providerAdapter = { anthropic: "claude_local", openai: "codex_local", openrouter: "opencode_local", xai: "grok_local" }[binding.provider]; + const probe = await requireServerAdapter(providerAdapter).testEnvironment({ ...context, adapterType: providerAdapter, config: { ...context.config, engine: "cli" } }); + result.checks.push(...probe.checks); + result.status = probe.status === "fail" ? "fail" : result.status === "warn" || probe.status === "warn" ? "warn" : "pass"; + } + if (!result.checks.some(check => /hello_probe_(passed|succeeded)$/.test(check.code))) { + result.status = "fail"; + result.checks.push({ code: "ai_connection_validation_incomplete", level: "error", message: "The selected account has not completed a provider hello test. Retry before adopting it." }); + } + return result; + } + + async function validateManagedAgentBinding(req: Request, companyId: string, agentId: string, adapterType: string, config: Record, binding: AiConnectionBinding, environmentId: string | null | undefined, test: boolean, newAgent = false) { + const userId = responsibleUserForAiRequest(req); + const allowUninstalledShared = newAgent && await canInstallSharedAiConnectionForNewAgent(db, req, companyId, binding); + const selection = await aiConnectionService(db).select({ companyId, agentId, userId, adapterType, model: config.model, runnerProvider: config.provider, acpxAgent: config.acpxAgent, binding, allowUninstalledPersonal: newAgent, allowUninstalledShared, allowLegacyValidation: test }); + if (test) { + if (environmentId) await assertAdapterTestEnvironmentForCompany(companyId, environmentId); + const target = await resolveAdapterTestExecutionContext({ companyId, adapterType, environmentId: environmentId ?? null }); + let managed: Awaited> | undefined; + try { + if (!target.executionTarget && target.fallbackChecks.some(check => check.level === "error")) throw unprocessable("The agent environment is not available for adoption"); + managed = await prepareManagedAiRuntime(db, { companyId, agentId, responsibleUserId: userId, adapterType, binding, config, allowUninstalledPersonal: newAgent, allowUninstalledShared, allowLegacyValidation: true }); + const result = await testManagedEnvironment(adapterType, { companyId, adapterType, config: managed.config, executionTarget: target.executionTarget, environmentName: target.environmentName }, binding); + if (result.status === "fail" || result.checks.some(check => check.code === ADAPTER_AUTH_MISSING_CHECK_CODE)) throw unprocessable("The selected AI connection failed validation in this agent’s environment"); + if (selection.connection.config.aiLegacyAdoption === true) await db.update(toolConnections).set({ healthStatus: "ok", config: { ...selection.connection.config, aiLegacyAdoption: false }, updatedAt: new Date() }).where(eq(toolConnections.id, selection.connection.id)); + } finally { try { await managed?.cleanup(); } finally { await target.release("released"); } } + } + return selection.connection.id; + } + router.post( "/companies/:companyId/adapters/:type/test-environment", validate(testAdapterEnvironmentSchema), @@ -3141,8 +3220,9 @@ export function agentRoutes( const adapter = requireServerAdapter(type); - const inputAdapterConfig = - (req.body?.adapterConfig ?? {}) as Record; + const aiBinding = req.body.aiConnection ? aiConnectionBindingSchema.parse(req.body.aiConnection) : undefined; + if (aiBinding && req.body.testCredentials && Object.keys(req.body.testCredentials).length) throw unprocessable("A managed connection test cannot override its credentials"); + const inputAdapterConfig = aiBinding ? { ...req.body.adapterConfig, env: stripAiAuthBindings(req.body.adapterConfig?.env) } : (req.body?.adapterConfig ?? {}) as Record; const requestedEnvironmentId = typeof req.body?.environmentId === "string" && req.body.environmentId.trim().length > 0 ? (req.body.environmentId as string) @@ -3215,7 +3295,7 @@ export function agentRoutes( if (requestedEnvironmentId) { const selectedEnvironment = await environmentsSvc.getById(requestedEnvironmentId); const environmentEnv = Object.fromEntries( - Object.entries(parseObject(selectedEnvironment?.envVars)).filter( + Object.entries(aiBinding ? stripAiAuthBindings(selectedEnvironment?.envVars) : parseObject(selectedEnvironment?.envVars)).filter( ([key]) => !isForbiddenConfigEnvKey(key), ), ); @@ -3295,13 +3375,12 @@ export function agentRoutes( effectiveAdapterConfig.apiKey = req.body.testCredentials.API_SERVER_KEY; } } - const result = await adapter.testEnvironment({ - companyId, - adapterType: type, - config: effectiveAdapterConfig, - executionTarget, - environmentName, - }); + const managed = aiBinding ? await prepareManagedAiRuntime(db, { companyId, agentId: req.body.agentId ?? "", responsibleUserId: responsibleUserForAiRequest(req), adapterType: type, binding: aiBinding, config: effectiveAdapterConfig, allowUninstalledPersonal: !req.body.agentId, allowUninstalledShared: !req.body.agentId && await canInstallSharedAiConnectionForNewAgent(db, req, companyId, aiBinding) }) : null; + let result; + try { + result = managed && aiBinding ? await testManagedEnvironment(type, { companyId, adapterType: type, config: managed.config, executionTarget, environmentName }, aiBinding) : await adapter.testEnvironment({ companyId, adapterType: type, config: effectiveAdapterConfig, executionTarget, environmentName }); + if (managed) result.checks.unshift({ code: "ai_connection_tested", level: "info", message: `Tested ${managed.accountName} — ${managed.accountOwnerUserId ? managed.accountOwnerUserId === responsibleUserForAiRequest(req) ? "your personal account" : "the owner’s account authorized for this agent" : "company-shared account"}. Responsible user: ${req.actor.type === "agent" ? responsibleUserForAiRequest(req) ?? "unavailable" : "the signed-in user"}.` }); + } finally { await managed?.cleanup(); } const prefixChecks = [ ...(sandboxIdentityCheck ? [sandboxIdentityCheck] : []), @@ -3483,6 +3562,10 @@ export function agentRoutes( if (!resolved) return; const { ownerUserId: startedByUserId, data } = resolved; + if (data.aiConnection) { + await assertAiConnectionCreateAccess(db, req, companyId, data.aiConnection); + if (!isAiConnectionCompatible(data.aiConnection, type)) throw unprocessable("Incompatible login method"); + } const controller = new AbortController(); let result: Awaited>; try { @@ -3492,6 +3575,7 @@ export function agentRoutes( adapterType: type, startedByUserId, ttlSeconds: data.ttlSeconds, + aiConnection: data.aiConnection, signal: controller.signal, }); } catch (error) { @@ -4292,6 +4376,8 @@ export function agentRoutes( const requiresApproval = company.requireBoardApprovalForNewAgents; const status = requiresApproval ? "pending_approval" : "idle"; + const managedHireBinding = normalizedHireInput.runtimeConfig?.aiConnection ? aiConnectionBindingSchema.parse(normalizedHireInput.runtimeConfig.aiConnection) : undefined; + const managedHireConnectionId = managedHireBinding ? await validateManagedAgentBinding(req, companyId, hiredAgentId, normalizedHireInput.adapterType, normalizedHireInput.adapterConfig, managedHireBinding, normalizedHireInput.defaultEnvironmentId, false, true) : undefined; const createdAgent = await svc.create( companyId, { @@ -4302,6 +4388,7 @@ export function agentRoutes( lastHeartbeatAt: null, }, { + aiConnectionInstall: managedHireConnectionId ? { connectionId: managedHireConnectionId, createdByUserId: responsibleUserForAiRequest(req) } : undefined, claudeLogin: { storedSessionId: hireStoredSessionId ?? null, ownerUserId: req.actor.type === "agent" ? null : (req.actor.userId ?? null), @@ -4528,6 +4615,8 @@ export function agentRoutes( allowedSandboxProviders: allowedSandboxProvidersForAgent(createInput.adapterType), }); + const managedBinding = normalizedRuntimeConfig.aiConnection ? aiConnectionBindingSchema.parse(normalizedRuntimeConfig.aiConnection) : undefined; + const managedConnectionId = managedBinding ? await validateManagedAgentBinding(req, companyId, agentId, createInput.adapterType, normalizedAdapterConfig, managedBinding, createInput.defaultEnvironmentId, false, true) : undefined; const createdAgent = await svc.create( companyId, { @@ -4540,6 +4629,7 @@ export function agentRoutes( lastHeartbeatAt: null, }, { + aiConnectionInstall: managedConnectionId ? { connectionId: managedConnectionId, createdByUserId: responsibleUserForAiRequest(req) } : undefined, claudeLogin: { storedSessionId: createStoredSessionId ?? null, ownerUserId: req.actor.type === "agent" ? null : (req.actor.userId ?? null), @@ -5043,6 +5133,15 @@ export function agentRoutes( adapterConfig: patchData.adapterConfig, }); } + if (existing.runtimeConfig.aiConnection && requestedRuntimeConfig && !requestedRuntimeConfig.aiConnection) requestedRuntimeConfig.aiConnection = existing.runtimeConfig.aiConnection; + const nextAiBinding = aiConnectionBindingSchema.safeParse(requestedRuntimeConfig?.aiConnection ?? existing.runtimeConfig.aiConnection).data; + if (nextAiBinding) { + await assertCanUpdateAgent(req, existing); + const changed = JSON.stringify(nextAiBinding) !== JSON.stringify(existing.runtimeConfig.aiConnection); + const aiConfig = (patchData.adapterConfig ?? existing.adapterConfig) as Record; + if (!isAiConnectionCompatible(nextAiBinding, requestedAdapterType, aiConfig.model, aiConfig.provider, aiConfig.acpxAgent)) throw unprocessable("Select an AI connection compatible with the new harness and model"); + if (changed) await validateManagedAgentBinding(req, existing.companyId, existing.id, requestedAdapterType, aiConfig, nextAiBinding, (patchData.defaultEnvironmentId !== undefined ? patchData.defaultEnvironmentId : existing.defaultEnvironmentId) as string | null, true); + } if (requestedRuntimeConfig) patchData.runtimeConfig = requestedRuntimeConfig; if (touchesAdapterConfiguration || Object.prototype.hasOwnProperty.call(patchData, "defaultEnvironmentId")) { await assertAgentDefaultEnvironmentSelection( @@ -5446,7 +5545,7 @@ export function agentRoutes( return; } } else { - await assertBoardCanManageAgentsForCompany(req, agent.companyId); + await assertBoardCanWakeAgent(req, agent); } if (req.body.debug?.providerTrace === "raw") { assertInstanceAdmin(req); @@ -5488,6 +5587,23 @@ export function agentRoutes( typeof failedContext.issueId === "string" ? failedContext.issueId : null; + if (issueId) { + const issue = await issueService(db).getById(issueId); + if (!issue || issue.companyId !== agent.companyId) throw notFound("Task not found"); + if (issue.conversationAgentId && issue.conversationUserId !== req.actor.userId) { + throw forbidden("Only the conversation owner can retry a chat run"); + } + const decision = await access.decide({ + actor: req.actor, action: "issue:comment", + resource: { + type: "issue", companyId: issue.companyId, issueId: issue.id, + projectId: issue.projectId, parentIssueId: issue.parentId, + assigneeAgentId: issue.assigneeAgentId, assigneeUserId: issue.assigneeUserId, status: issue.status, + }, + }); + if (!decision.allowed) throw forbidden(decision.explanation, authorizationDeniedDetails(decision)); + if (issue.assigneeAgentId !== agent.id) throw conflict("The task is no longer assigned to this agent."); + } const chatBinding = issueId ? await db .select({ id: chatConversations.id }) @@ -5554,6 +5670,7 @@ export function agentRoutes( } const run = await heartbeat.wakeup(id, { failedRunId: req.body.failedRunId ?? null, + ...(req.actor.type === "board" && !req.body.failedRunId ? { manualUserWake: true } : {}), source: opts.source, triggerDetail: req.body.triggerDetail ?? "manual", reason: req.body.reason ?? null, @@ -5648,7 +5765,7 @@ export function agentRoutes( return; } } else { - await assertBoardCanManageAgentsForCompany(req, agent.companyId); + await assertBoardCanWakeAgent(req, agent); } const providerTraceRequested = req.body?.debug?.providerTrace === "raw"; if (providerTraceRequested) { @@ -5687,6 +5804,7 @@ export function agentRoutes( } } const wakeOpts: Parameters[1] = { + ...(req.actor.type === "board" ? { manualUserWake: true } : {}), source: "on_demand", triggerDetail: typeof body.triggerDetail === "string" ? body.triggerDetail as "manual" | "system" | "ping" | "callback" : "manual", requestedByActorType: req.actor.type === "agent" ? "agent" : "user", @@ -6046,6 +6164,10 @@ export function agentRoutes( if (!resolved) return; const { ownerUserId, data } = resolved; const { environmentId, adapterType } = data; + if (data.aiConnection) { + await assertAiConnectionCreateAccess(db, req, companyId, data.aiConnection); + if (!isAiConnectionCompatible(data.aiConnection, adapterType)) throw unprocessable("Incompatible login method"); + } const confirmedOverwrite: ClaudeSetupTokenOverwrite | null = data.overwrite ?? null; const scope: SetupTokenSessionScope = { @@ -6054,6 +6176,7 @@ export function agentRoutes( adapterType, environmentId, confirmedOverwrite, + aiConnection: data.aiConnection, }; // Read the panel mode from the adapter capability. The guard already checked // the capability, so it is present here. The client renders the panel from @@ -6110,7 +6233,7 @@ export function agentRoutes( ? { authorizationUrl: descriptor.loginUrl, transportAdvisory: assessSetupTokenTransport(req) } : null, }; - res.json(body); + res.json({ ...body, ...(descriptor.aiConnection ? { aiConnection: descriptor.aiConnection } : {}) }); }); router.get("/companies/:companyId/setup-token-login-sessions/:sessionId", async (req, res) => { diff --git a/server/src/routes/ai-connections.ts b/server/src/routes/ai-connections.ts new file mode 100644 index 0000000000..da293a37a1 --- /dev/null +++ b/server/src/routes/ai-connections.ts @@ -0,0 +1,371 @@ +import { supportsLocalAiLogin } from "../services/local-ai-login-policy.js"; +import { readVerifiedLocalAiCredential } from "../services/local-ai-credentials.js"; +import { localAiLoginService } from "../services/local-ai-login.js"; +import { z } from "zod"; +import { Router, type Request } from "express"; +import { and, eq, inArray, sql } from "drizzle-orm"; +import { + type Db, + adapterAuthSessions, + heartbeatRuns, + toolConnections, + connectionGrants, + agents, +} from "@paperclipai/db"; +import { + createAiConnectionSchema, + aiConnectionLoginIntentSchema, + localAiConnectionSchema, + localAiLoginStartSchema, + isAiConnectionCompatible, + type AiConnectionLoginIntent, + type AiProvider, + type AiConnectionBinding, +} from "@paperclipai/shared"; +import { assertBoard, assertCompanyAccess, getActorInfo } from "./authz.js"; +import { forbidden, notFound, unprocessable } from "../errors.js"; +import { accessService } from "../services/access.js"; +import { logActivity } from "../services/activity-log.js"; +import { aiConnectionService } from "../services/ai-connections.js"; +import { validate } from "../middleware/validate.js"; + +/** Agent API calls inherit authenticated run identity, never the agent's own ID. */ +export function responsibleUserForAiRequest(req: Request): string | null { + return req.actor.type === "agent" + ? req.actor.onBehalfOfUserId ?? null + : getActorInfo(req).actorId; +} + +export async function assertAiConnectionCreateAccess( + db: Db, + req: Request, + companyId: string, + input: Pick< + AiConnectionLoginIntent, + "ownership" | "allAgents" | "agentIds" | "connectionId" + >, +) { + assertBoard(req); + assertCompanyAccess(req, companyId); + const actor = getActorInfo(req); + const userId = actor.actorId; + if (input.connectionId) { + const [grant] = await db + .select({ + owner: connectionGrants.subjectUserId, + creator: toolConnections.createdByUserId, + }) + .from(connectionGrants) + .innerJoin( + toolConnections, + eq(toolConnections.id, connectionGrants.connectionId), + ) + .where( + and( + eq(toolConnections.id, input.connectionId), + eq(toolConnections.companyId, companyId), + eq(toolConnections.connectionPurpose, "ai"), + ), + ) + .limit(1); + if (!grant || (grant.owner ?? grant.creator) !== userId) + throw forbidden( + "Only the account owner can reconnect this AI connection", + ); + } + const membership = req.actor.memberships?.find( + (m) => m.companyId === companyId && m.status === "active", + ); + const manager = + req.actor.source === "local_implicit" || + req.actor.isInstanceAdmin || + membership?.membershipRole === "owner" || + membership?.membershipRole === "admin" || + (await accessService(db).hasPermission( + companyId, + "user", + userId, + "tools:manage_connections", + )); + if ( + !input.connectionId && + !manager && + (input.ownership === "shared" || input.allAgents) + ) + throw forbidden( + "A connection manager must authorize company-shared access", + ); + if (!input.connectionId && !manager && input.agentIds.length) { + for (const id of input.agentIds) { + if ( + !( + await accessService(db).decide({ + actor: { type: "board", userId }, + action: "agent_config:update", + resource: { type: "agent", companyId, agentId: id }, + }) + ).allowed + ) + throw forbidden("You cannot configure this agent"); + } + } + if (membership?.membershipRole === "viewer") + throw forbidden("Viewers cannot create AI connections"); + return userId; +} + +/** Creating an agent may install a shared connection only with the existing + * connection-configure authority. An agent actor cannot grant itself access. */ +export async function canInstallSharedAiConnectionForNewAgent( + db: Db, req: Request, companyId: string, binding: AiConnectionBinding, +): Promise { + if (req.actor.type !== "board" || binding.mode !== "shared") return false; + assertCompanyAccess(req, companyId); + const member = req.actor.memberships?.find(m => m.companyId === companyId && m.status === "active"); + if (member?.membershipRole === "viewer") return false; + const userId = getActorInfo(req).actorId; + const [connection] = await db.select({ creator: toolConnections.createdByUserId }) + .from(toolConnections).where(and(eq(toolConnections.companyId, companyId), + eq(toolConnections.id, binding.connectionId), eq(toolConnections.connectionPurpose, "ai"))); + if (!connection) return false; + return req.actor.source === "local_implicit" || req.actor.isInstanceAdmin === true || + connection.creator === userId || await accessService(db).hasPermission(companyId, "user", userId, "tools:manage_connections"); +} + +/** Fixed provider endpoints; credentials are never sent to a caller-supplied URL or through a redirect. */ +export async function validateAiApiKey( + provider: AiProvider, + key: string, + request: typeof fetch = fetch, +) { + const endpoints = { + anthropic: "https://api.anthropic.com/v1/models?limit=1", + openai: "https://api.openai.com/v1/models", + openrouter: "https://openrouter.ai/api/v1/key", + xai: "https://api.x.ai/v1/models", + }; + let response: Response; + try { + response = await request(endpoints[provider], { + redirect: "error", + signal: AbortSignal.timeout(15000), + headers: + provider === "anthropic" + ? { "x-api-key": key, "anthropic-version": "2023-06-01" } + : { Authorization: `Bearer ${key}` }, + }); + } catch { + throw unprocessable("Could not verify the account. Try again."); + } + await response.body?.cancel(); + if (!response.ok) + throw unprocessable( + response.status === 401 || response.status === 403 + ? "The provider rejected this API key." + : "The provider could not verify this account. Try again.", + ); +} + +export function aiConnectionRoutes(db: Db, options: Parameters[0] = {}) { + function assertLocalLoginAvailable() { + if (!supportsLocalAiLogin(options)) throw unprocessable("Server-host subscription sign-in is unavailable on this hosted instance. Choose a supported sign-in environment or use an API key."); + } + const router = Router(); + const service = aiConnectionService(db); + const localLogin = localAiLoginService(db); + function assertLocalOperator(req: Request) { + assertBoard(req); + assertCompanyAccess(req, req.params.companyId as string); + if (req.actor.source !== "local_implicit") + throw forbidden("Only the local operator can connect this machine's CLI account."); + } + router.post("/companies/:companyId/ai-connections/local/attempts", validate(localAiLoginStartSchema), async (req, res) => { + const companyId = req.params.companyId as string; + const { restart, ...intent } = localAiLoginStartSchema.parse(req.body); + assertLocalLoginAvailable(); + const userId = await assertAiConnectionCreateAccess(db, req, companyId, intent); + res.setHeader("Cache-Control", "no-store"); + res.status(201).json(await localLogin.start(companyId, userId, intent, restart)); + }); + router.post("/companies/:companyId/ai-connections/local/check", validate(localAiConnectionSchema), async (req, res) => { + const companyId = req.params.companyId as string; + const { localSessionId, ...intent } = localAiConnectionSchema.parse(req.body); + assertLocalLoginAvailable(); + // Only implicit local operators may inspect ambient Claude credentials. + // Authenticated users sign in to their own company/user-scoped attempt. + if (intent.provider === "anthropic" && !localSessionId) assertLocalOperator(req); + const userId = await assertAiConnectionCreateAccess(db, req, companyId, intent); + res.setHeader("Cache-Control", "no-store"); + res.json(await localLogin.check(companyId, userId, intent, localSessionId)); + }); + router.delete("/companies/:companyId/ai-connections/local/attempts/:sessionId", async (req, res) => { + assertBoard(req); + assertCompanyAccess(req, req.params.companyId as string); + const id = z.string().uuid().parse(req.params.sessionId); + await localLogin.cancel(req.params.companyId as string, getActorInfo(req).actorId, id); + res.json({ ok: true }); + }); + router.get("/companies/:companyId/ai-connections", async (req, res) => { + const companyId = req.params.companyId as string; + assertBoard(req); + assertCompanyAccess(req, companyId); + const currentUserId = getActorInfo(req).actorId; + res.setHeader("Cache-Control", "no-store"); + const agentId = req.query.agentId; + if (agentId !== undefined && !z.string().uuid().safeParse(agentId).success) + throw unprocessable("Invalid agent ID"); + res.json({ + currentUserId, + connections: await service.list( + companyId, + currentUserId, + agentId as string | undefined, + ), + }); + }); + router.get( + "/companies/:companyId/ai-connections/:connectionId/active-runs", + async (req, res) => { + const companyId = req.params.companyId as string; + assertBoard(req); + assertCompanyAccess(req, companyId); + if (!z.string().uuid().safeParse(req.params.connectionId).success) + throw unprocessable("Invalid connection ID"); + const [connection] = await db + .select() + .from(toolConnections) + .where( + and( + eq(toolConnections.companyId, companyId), + eq(toolConnections.id, req.params.connectionId as string), + eq(toolConnections.connectionPurpose, "ai"), + ), + ); + if (!connection || !(await service.list(companyId, getActorInfo(req).actorId)).some(account => account.id === connection.id)) + throw notFound("AI connection not found"); + res.setHeader("Cache-Control", "no-store"); + res.json( + await db + .select({ + id: heartbeatRuns.id, + agentId: agents.id, + agentName: agents.name, + status: heartbeatRuns.status, + }) + .from(heartbeatRuns) + .innerJoin(agents, eq(agents.id, heartbeatRuns.agentId)) + .where( + and( + eq(heartbeatRuns.companyId, companyId), + inArray(heartbeatRuns.status, ["queued", "running"]), + sql`${heartbeatRuns.contextSnapshot}->'aiConnection'->>'connectionId' = ${connection.id}`, + ), + ), + ); + }, + ); + router.post( + "/companies/:companyId/ai-connections", + validate(createAiConnectionSchema), + async (req, res) => { + const companyId = req.params.companyId as string; + const input = createAiConnectionSchema.parse(req.body); + const userId = await assertAiConnectionCreateAccess( + db, + req, + companyId, + input, + ); + if (input.method !== "api_key") + throw unprocessable( + "Use the existing provider sign-in flow to connect a subscription", + ); + const attemptStartedAt = new Date(); + await validateAiApiKey(input.provider, input.apiKey!); + const result = await service.save( + companyId, + userId, + input, + input.apiKey!, + undefined, + attemptStartedAt, + ); + res.status(201).json(result); + }, + ); + router.post( + "/companies/:companyId/ai-connections/local", + validate(localAiConnectionSchema), + async (req, res) => { + const companyId = req.params.companyId as string; + const { localSessionId, ...input } = localAiConnectionSchema.parse(req.body); + assertLocalLoginAvailable(); + if (input.provider === "anthropic" && !localSessionId) assertLocalOperator(req); + const userId = await assertAiConnectionCreateAccess(db, req, companyId, input); + if (localSessionId || input.provider === "openai" || input.provider === "xai") { + if (!localSessionId) throw unprocessable("Start a separate local sign-in for this connection before connecting."); + res.status(201).json(await localLogin.complete(companyId, userId, localSessionId, input)); + return; + } + const attemptStartedAt = new Date(); + const credential = await readVerifiedLocalAiCredential(input.provider); + res.status(201).json(await service.save(companyId, userId, input, credential, undefined, attemptStartedAt)); + }, + ); + router.put( + "/companies/:companyId/ai-connections/default", + async (req, res) => { + const companyId = req.params.companyId as string; + assertBoard(req); + assertCompanyAccess(req, companyId); + const userId = getActorInfo(req).actorId; + if ( + req.actor.memberships?.some( + (m) => m.companyId === companyId && m.membershipRole === "viewer", + ) + ) + throw forbidden("Viewers cannot change defaults"); + if (!z.string().uuid().safeParse(req.body.grantId).success) + throw unprocessable("Choose a personal connection"); + await service.setDefault(companyId, userId, req.body.grantId); + await logActivity(db, { + companyId, + actorType: "user", + actorId: userId, + action: "ai_connection.default_changed", + entityType: "connection_grant", + entityId: req.body.grantId, + }); + res.json({ ok: true }); + }, + ); + router.get( + "/companies/:companyId/ai-connections/login/:sessionId", + async (req, res) => { + const companyId = req.params.companyId as string; + assertBoard(req); + assertCompanyAccess(req, companyId); + const [session] = await db + .select({ + connectionId: adapterAuthSessions.connectionId, + grantId: adapterAuthSessions.connectionGrantId, + }) + .from(adapterAuthSessions) + .where( + and( + eq(adapterAuthSessions.companyId, companyId), + eq(adapterAuthSessions.startedByUserId, getActorInfo(req).actorId), + eq( + adapterAuthSessions.publicSessionId, + req.params.sessionId as string, + ), + ), + ); + if (!session?.connectionId) + throw notFound("The login has not saved a connection"); + res.setHeader("Cache-Control", "no-store"); + res.json(session); + }, + ); + return router; +} diff --git a/server/src/routes/chat-channels.ts b/server/src/routes/chat-channels.ts index a080302400..f0eac96246 100644 --- a/server/src/routes/chat-channels.ts +++ b/server/src/routes/chat-channels.ts @@ -7,6 +7,7 @@ import type { Db } from "@paperclipai/db"; import { CHAT_PROVIDERS, configureChatEndpointSchema, + inspectPhotonProjectSchema, confirmChatIdentityLinkSchema, createChatEndpointSchema, createChatIdentityLinkIntentSchema, @@ -153,6 +154,12 @@ export function chatChannelRoutes(db: Db, options: ChatChannelRouteOptions) { }, ); + router.post("/chat-endpoints/:endpointId/photon/inspect", validate(inspectPhotonProjectSchema), async (req, res) => { + if (!(await assertEndpointManagementAccess(req, res))) return; + res.set("Cache-Control", "no-store"); + res.json(await service.inspectPhoton(endpointId(req), req.body)); + }); + router.post( "/chat-endpoints/:endpointId/setup", validate(configureChatEndpointSchema), diff --git a/server/src/routes/connection-intents.ts b/server/src/routes/connection-intents.ts index e257dcf9cc..27b4c45892 100644 --- a/server/src/routes/connection-intents.ts +++ b/server/src/routes/connection-intents.ts @@ -180,8 +180,10 @@ export function connectionIntentBoardRoutes(db: Db, heartbeat: Heartbeat) { } router.get("/connection-intents/:interactionId/setup-options", async (req, res) => { - await addressedIntent(req); - res.json(await service.setupOptions(req.params.interactionId as string)); + const { loaded } = await addressedIntent(req); + res.json(await service.setupOptions(req.params.interactionId as string, { + canManageOrganizationGrant: await canManageCompanyConnections(req, loaded.issue.companyId), + })); }); router.post("/connection-intents/:interactionId/phase", async (req, res) => { diff --git a/server/src/routes/health.ts b/server/src/routes/health.ts index 0e4d954722..bf7dc4a4a0 100644 --- a/server/src/routes/health.ts +++ b/server/src/routes/health.ts @@ -1,3 +1,4 @@ +import { supportsLocalAiLogin } from "../services/local-ai-login-policy.js"; import { randomUUID, timingSafeEqual } from "node:crypto"; import { Router } from "express"; import type { Db } from "@paperclipai/db"; @@ -391,6 +392,7 @@ export function healthRoutes( status: healthStatus, deploymentMode: opts.deploymentMode, deploymentExposure: opts.deploymentExposure, + localAiLoginSupported: supportsLocalAiLogin(opts), commit, bootstrapStatus, bootstrapInviteActive, @@ -414,6 +416,7 @@ export function healthRoutes( commit, deploymentMode: opts.deploymentMode, deploymentExposure: opts.deploymentExposure, + localAiLoginSupported: supportsLocalAiLogin(opts), authReady: opts.authReady, bootstrapStatus, bootstrapInviteActive, diff --git a/server/src/routes/issues.ts b/server/src/routes/issues.ts index 55deb1f8a1..81ed975078 100644 --- a/server/src/routes/issues.ts +++ b/server/src/routes/issues.ts @@ -357,6 +357,9 @@ const queuedCommentSteeringTargetSchema = queuedCommentMutationTargetSchema.extend({ targetRunId: z.string().min(1), }); +const queuedCommentInterruptTargetSchema = queuedCommentMutationTargetSchema.extend({ + targetRunId: z.string().min(1).nullable(), +}); const editQueuedCommentSchema = queuedCommentMutationTargetSchema.extend({ body: z .string() @@ -6943,9 +6946,10 @@ export function issueRoutes( actor: ReturnType; queueId: string; targetRunId?: string; + allowStoppedTarget?: boolean; }) { - await input.tx - .select({ id: issueRows.id }) + const [currentIssue] = await input.tx + .select() .from(issueRows) .where( and( @@ -6954,6 +6958,8 @@ export function issueRoutes( ), ) .for("update"); + if (!currentIssue) throw notFound("Issue not found"); + input.issue = currentIssue; const wake = await input.tx .select() .from(agentWakeupRequests) @@ -7030,7 +7036,7 @@ export function issueRoutes( and( eq(heartbeatRuns.id, activeRunId), eq(heartbeatRuns.companyId, input.issue.companyId), - eq(heartbeatRuns.status, "running"), + input.allowStoppedTarget ? undefined : eq(heartbeatRuns.status, "running"), ), ) .for("update") @@ -15216,12 +15222,14 @@ export function issueRoutes( router.post( "/issues/:id/queued-comments/interrupt", - validate(queuedCommentSteeringTargetSchema), + validate(queuedCommentInterruptTargetSchema), async (req, res) => { assertBoard(req); if (!req.actor.userId) throw forbidden("Board user context required"); const issue = await getAccessibleResource(req, res, svc.getById(req.params.id as string), "Issue not found"); if (!issue) return; + const decision = await decideIssueAccess(req, issue, "issue:comment"); + if (!decision.allowed) throw forbidden(decision.explanation, authorizationDeniedDetails(decision)); if (issue.conversationAgentId) { if (!(await instanceSettings.getExperimental()).enableAgentChat) throw notFound("Agent Chat is disabled"); if (req.actor.userId !== issue.conversationUserId) { @@ -15229,23 +15237,43 @@ export function issueRoutes( } } const actor = getActorInfo(req); - await db.transaction(async (tx) => { + const runToInterrupt = await db.transaction(async (tx) => { const locked = await lockQueuedCommentState({ - tx, issue, actor, queueId: req.body.queueId, targetRunId: req.body.targetRunId, + tx, issue, actor, queueId: req.body.queueId, targetRunId: req.body.targetRunId ?? undefined, + allowStoppedTarget: true, }); assertQueueMutationTarget({ queue: locked.queue, queueId: req.body.queueId, revision: req.body.revision }); - if (locked.queue.protocol !== "legacy" || locked.activeRun?.agentId !== issue.assigneeAgentId) { + if (locked.queue.protocol !== "legacy" || locked.state !== "deferred" || + !locked.queue.entries.length || + (locked.activeRun && locked.activeRun.agentId !== locked.wake.agentId)) { throw conflict("This queue does not support legacy interruption"); } + if (locked.activeRun && locked.activeRun.status !== "running" && + !["succeeded", "failed", "timed_out", "interrupted", "cancelled"].includes(locked.activeRun.status)) { + throw conflict("The previous run has not stopped"); + } + if (locked.activeRun?.status === "running" && locked.activeRun.id !== req.body.targetRunId) { + throw conflict("The queued message targets a stale run", { code: "queued_comment_stale_target" }); + } + // The click is durable fresh user intent, including when the message + // predates a failed run's stop. Keep its content and original attribution. + await tx.update(agentWakeupRequests).set({ + payload: { ...readObject(locked.wake.payload), queuedCommentInterrupt: { + actorId: actor.actorId, requestedAt: new Date().toISOString(), + } }, + updatedAt: new Date(), + }).where(eq(agentWakeupRequests.id, locked.wake.id)); + return locked.activeRun?.status === "running" ? locked.activeRun.id : null; }); // Never hold the issue lock while joining the adapter. Queue edits and // discards stay authoritative until the dispatcher claims the successor. const options = operatorInterruptCancelOptions({ issueId: issue.id, actor }); - await heartbeat.cancelRun(req.body.targetRunId, "Interrupted to send queued messages", { + if (runToInterrupt) await heartbeat.cancelRun(runToInterrupt, "Interrupted to send queued messages", { ...options, suppressImmediateRecovery: true, resultJson: { ...options.resultJson, queuedCommentInterruptQueueId: req.body.queueId }, }); + await heartbeat.resumeQueuedCommentInterrupt(issue.companyId, req.body.queueId, { retryCleanup: true }); await logActivity(db, { companyId: issue.companyId, actorType: actor.actorType, actorId: actor.actorId, agentId: actor.agentId, runId: actor.runId, agentApiKeyId: actor.agentApiKeyId, diff --git a/server/src/routes/openapi.ts b/server/src/routes/openapi.ts index c1f7a41f2b..44f5a81845 100644 --- a/server/src/routes/openapi.ts +++ b/server/src/routes/openapi.ts @@ -6,6 +6,10 @@ import { import { Router } from "express"; import { z } from "zod"; import { + createAiConnectionSchema, + aiConnectionLoginIntentSchema, + localAiConnectionSchema, + localAiLoginStartSchema, emailEndpointSetupSchema, emailConnectionSchema, emailSendSchema, @@ -262,6 +266,9 @@ import { confirmChatIdentityLinkSchema, createChatEndpointSchema, createChatIdentityLinkIntentSchema, + inspectPhotonProjectSchema, + photonProjectIdSchema, + photonLineIdSchema, publishChatPublicationSchema, resolveChatActionSchema, resolveChatPublicationSchema, @@ -794,6 +801,7 @@ const chatEndpointResponseSchema = z providerAccountId: z.string().nullable(), providerAccountLabel: z.string().nullable(), botExternalId: z.string().nullable(), + photonAllocation: z.enum(["dedicated", "shared"]).optional(), botUsername: z.string().nullable(), botLabel: z.string().nullable(), botAvatarUrl: z.string().nullable(), @@ -827,6 +835,7 @@ const chatEndpointResourceResponseSchema = z availability: chatResourceAvailabilitySchema, enabled: z.boolean(), metadata: z.record(z.string(), z.unknown()), + participants: z.array(z.string()).optional(), createdAt: z.string().datetime(), updatedAt: z.string().datetime(), }) @@ -1284,6 +1293,16 @@ const BOARD_ONLY_PREFIXES = [ ]; const BOARD_ONLY_OPERATIONS = new Set([ + "GET /api/companies/{companyId}/ai-connections", + "POST /api/companies/{companyId}/ai-connections", + "POST /api/companies/{companyId}/ai-connections/local", + "POST /api/companies/{companyId}/ai-connections/local/attempts", + "POST /api/companies/{companyId}/ai-connections/local/check", + "DELETE /api/companies/{companyId}/ai-connections/local/attempts/{sessionId}", + "PUT /api/companies/{companyId}/ai-connections/default", + "GET /api/companies/{companyId}/ai-connections/{connectionId}/active-runs", + "GET /api/companies/{companyId}/ai-connections/login/{sessionId}", + "GET /api/companies/{companyId}/project-repositories", "PUT /api/projects/{id}/repositories", "DELETE /api/issues/{id}/documents/{key}", @@ -1455,6 +1474,7 @@ const BOARD_ONLY_OPERATIONS = new Set([ "POST /api/chat-endpoints/{endpointId}/setup", "POST /api/chat-endpoints/{endpointId}/setup-secret", "POST /api/chat-endpoints/{endpointId}/test", + "POST /api/chat-endpoints/{endpointId}/photon/inspect", "GET /api/chat-endpoints/{endpointId}/resources", "PUT /api/chat-endpoints/{endpointId}/resources", "GET /api/chat-endpoints/{endpointId}/principals", @@ -2131,7 +2151,7 @@ registry.registerPath({ tags: ["chat-channels"], summary: "Configure or change chat endpoint lifecycle state", description: - "Runs a setup or lifecycle action. `configure` and `reconnect` accept provider credentials (Slack: `botToken`, `signingSecret`; GitHub: `appId`, `privateKey` after Paperclip generates the webhook secret; Discord: `applicationId`, `guildId`, `botToken`; Microsoft Teams: `clientId`, `tenantId`, `clientSecret`; Telegram: `botToken`). Credentials are stored as Paperclip secret references and are never returned. Other actions do not require credentials.", + "Runs a setup or lifecycle action. `configure` and `reconnect` accept provider credentials (Slack: `botToken`, `signingSecret`; GitHub: `appId`, `privateKey` after Paperclip generates the webhook secret; Discord: `applicationId`, `guildId`, `botToken`; Microsoft Teams: `clientId`, `tenantId`, `clientSecret`; Telegram: `botToken`; iMessage Photon: `projectSecret`, with nonsecret `photon.projectId` and `photon.lineId` configuration). Credentials are stored as Paperclip secret references and are never returned. Other actions do not require credentials.", request: { params: z.object({ endpointId: z.string().uuid() }), body: jsonBody(configureChatEndpointSchema), @@ -2144,6 +2164,9 @@ registry.registerPath({ 404: r.notFound, 409: r.conflict, 422: r.unprocessable, + 429: { description: "Provider request limit reached; retry later" }, + 502: { description: "Provider returned an invalid response; inspect provider health" }, + 503: { description: "Provider temporarily unavailable; retry later" }, }, }); @@ -2165,13 +2188,48 @@ registry.registerPath({ }, }); +registry.registerPath({ + method: "post", + path: "/api/chat-endpoints/{endpointId}/photon/inspect", + tags: ["chat-channels"], + summary: "Inspect Photon shared project or dedicated numbers for channel setup", + description: + "Requires a board user with connection-management access. The project secret is write-only input. Returns the project's actual allocation and eligibility for shared DMs or dedicated lines, never project secrets or minted line tokens. Responses are not cached. Inspection alone does not activate the channel.", + request: { + params: z.object({ endpointId: z.string().uuid() }), + body: jsonBody(inspectPhotonProjectSchema), + }, + responses: { + 200: r.ok(z.object({ + projectId: photonProjectIdSchema, + projectName: z.string(), + allocation: z.enum(["dedicated", "shared"]), + eligible: z.boolean(), + lines: z.array(z.object({ + lineId: photonLineIdSchema, + phoneNumber: z.string(), + eligible: z.boolean(), + unavailableReason: z.string().optional(), + }).strict()), + }).strict()), + 400: r.badRequest, + 401: r.unauthorized, + 403: r.forbidden, + 404: r.notFound, + 422: r.unprocessable, + 429: { description: "Photon request limit reached; retry later" }, + 502: { description: "Photon returned an invalid response; inspect provider health" }, + 503: { description: "Photon temporarily unavailable; retry later" }, + }, +}); + registry.registerPath({ method: "post", path: "/api/chat-endpoints/{endpointId}/test", tags: ["chat-channels"], summary: "Complete a chat endpoint setup test", description: - "Activates a verifying endpoint only after Paperclip has received a real provider event since the server-issued setup test boundary.", + "Activates a verifying endpoint only after Paperclip has received a real provider event since the server-issued setup test boundary. iMessage Photon additionally requires a fresh linked sender's task and a successful outbound agent publication.", request: { params: z.object({ endpointId: z.string().uuid() }) }, responses: { 200: r.ok(chatEndpointResponseSchema), @@ -2188,7 +2246,7 @@ registry.registerPath({ tags: ["chat-channels"], summary: "List destinations discovered for a chat endpoint", description: - "Lists provider destinations such as Slack and Discord channels, Teams channels, GitHub repositories, and Telegram chats. Direct-message resources are intentionally omitted.", + "Lists provider destinations such as Slack and Discord channels, Teams channels, GitHub repositories, Telegram chats, and iMessage Photon groups. Direct-message resources are intentionally omitted.", request: { params: z.object({ endpointId: z.string().uuid() }) }, responses: { 200: r.ok(z.array(chatEndpointResourceResponseSchema)), @@ -9879,6 +9937,51 @@ registerCurrentRoute({ body: declineConnectionIntentSchema, }); +// --- AI runtime connections ------------------------------------------------- + +registerCurrentRoute({ + method: "get", + path: "/api/companies/{companyId}/ai-connections", + tags: ["ai-connections"], + summary: "List available AI connections and personal defaults", + query: z.object({ agentId: z.string().uuid().optional() }), + responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized, 403: r.forbidden, 404: r.notFound, 422: r.unprocessable }, +}); + +registerCurrentRoute({ + method: "post", + path: "/api/companies/{companyId}/ai-connections", + tags: ["ai-connections"], + summary: "Validate and connect an AI API key, or reconnect its existing grant", + body: createAiConnectionSchema, + responses: { 201: r.ok(), 400: r.badRequest, 401: r.unauthorized, 403: r.forbidden, 404: r.notFound, 422: r.unprocessable }, +}); + +registerCurrentRoute({ + method: "put", + path: "/api/companies/{companyId}/ai-connections/default", + tags: ["ai-connections"], + summary: "Set the signed-in owner’s personal AI default", + body: z.object({ grantId: z.string().uuid() }), + responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized, 403: r.forbidden, 404: r.notFound, 422: r.unprocessable }, +}); + +registerCurrentRoute({ + method: "get", + path: "/api/companies/{companyId}/ai-connections/{connectionId}/active-runs", + tags: ["ai-connections"], + summary: "List active runs attributed to an AI connection", + responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized, 403: r.forbidden, 404: r.notFound, 422: r.unprocessable }, +}); + +registerCurrentRoute({ + method: "get", + path: "/api/companies/{companyId}/ai-connections/login/{sessionId}", + tags: ["ai-connections"], + summary: "Get the connection saved by an owned completed login", + responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized, 403: r.forbidden, 404: r.notFound, 422: r.unprocessable }, +}); + // --- Tool access ------------------------------------------------------------- registerCurrentRoute({ @@ -11026,3 +11129,32 @@ export function openApiRoutes() { }); return router; } + +registerCurrentRoute({ + method: "post", + path: "/api/companies/{companyId}/ai-connections/local", + tags: ["ai-connections"], + summary: "Verify and save the local operator's CLI subscription account", + body: localAiConnectionSchema, + responses: { 201: r.ok(), 400: r.badRequest, 401: r.unauthorized, 403: r.forbidden, 422: r.unprocessable }, +}); +registerCurrentRoute({ + method: "post", + path: "/api/companies/{companyId}/ai-connections/local/attempts", + tags: ["ai-connections"], summary: "Prepare an isolated local subscription sign-in", + body: localAiLoginStartSchema, + responses: { 201: r.ok(), 400: r.badRequest, 401: r.unauthorized, 403: r.forbidden, 422: r.unprocessable }, +}); +registerCurrentRoute({ + method: "delete", + path: "/api/companies/{companyId}/ai-connections/local/attempts/{sessionId}", + tags: ["ai-connections"], summary: "Cancel an owned local subscription sign-in", + responses: { 200: r.ok(), 401: r.unauthorized, 403: r.forbidden, 404: r.notFound }, +}); +registerCurrentRoute({ + method: "post", + path: "/api/companies/{companyId}/ai-connections/local/check", + tags: ["ai-connections"], summary: "Check the local operator's subscription sign-in without saving a connection", + body: localAiConnectionSchema, + responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized, 403: r.forbidden, 404: r.notFound, 422: r.unprocessable }, +}); diff --git a/server/src/services/agents.ts b/server/src/services/agents.ts index dfb9d3e3e0..d380043fee 100644 --- a/server/src/services/agents.ts +++ b/server/src/services/agents.ts @@ -3,6 +3,7 @@ import { and, desc, eq, gte, inArray, lt, ne, or, sql } from "drizzle-orm"; import type { Db } from "@paperclipai/db"; import { agents, + toolConnectionInstalls, agentConfigRevisions, agentApiKeys, agentRuntimeState, @@ -113,6 +114,7 @@ interface UpdateAgentOptions { } interface CreateAgentOptions { + aiConnectionInstall?: { connectionId: string; createdByUserId: string | null }; allowBuiltInAgentMetadata?: boolean; claudeLogin?: ClaudeLoginContext; } @@ -857,6 +859,13 @@ export function agentService(db: Db) { }) .returning() .then((rows) => rows[0]); + if (options?.aiConnectionInstall) { + await tx.insert(toolConnectionInstalls).values({ + companyId, connectionId: options.aiConnectionInstall.connectionId, + targetType: "agent", targetId: created.id, + createdByUserId: options.aiConnectionInstall.createdByUserId, + }).onConflictDoNothing(); + } await syncAgentSecretBindings(created, txDb); const normalizedCreated = await agentService(txDb).getById(created.id); if (!normalizedCreated) { diff --git a/server/src/services/ai-connection-runtime.ts b/server/src/services/ai-connection-runtime.ts new file mode 100644 index 0000000000..766a70d975 --- /dev/null +++ b/server/src/services/ai-connection-runtime.ts @@ -0,0 +1,388 @@ +import { createHash } from "node:crypto"; +import { unprocessable } from "../errors.js"; +import { mkdtemp, mkdir, writeFile, readFile, rm } from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { and, eq } from "drizzle-orm"; +import { type Db, connectionGrants } from "@paperclipai/db"; +import { + AI_CONNECTION_CAPABILITIES, + type AiConnectionBinding, +} from "@paperclipai/shared"; +import { aiConnectionService } from "./ai-connections.js"; +import { secretService } from "./secrets.js"; +import { decideCodexAuthMerge } from "@paperclipai/adapter-codex-local/server"; +import type { AdapterExecutionTarget } from "@paperclipai/adapter-utils/execution-target"; +import { runAdapterExecutionTargetProcess } from "@paperclipai/adapter-utils/execution-target"; +import { decideGrokAuthMerge } from "@paperclipai/adapter-grok-local/server"; + +// Blank values intentionally override inherited credentials in CLI child environments. +export const AI_AUTH_ENV_KEYS = [ + "ANTHROPIC_API_KEY", + "ANTHROPIC_AUTH_TOKEN", + "CLAUDE_CODE_OAUTH_TOKEN", + "OPENAI_API_KEY", + "CODEX_API_KEY", + "OPENROUTER_API_KEY", + "XAI_API_KEY", + "GROK_API_KEY", + "CODEX_HOME", + "GROK_HOME", + "CLAUDE_CONFIG_DIR", + "OPENCODE_AUTH_JSON", + "OPENCODE_CONFIG_CONTENT", + "OPENCODE_CONFIG", + "OPENCODE_CONFIG_DIR", + "PAPERCLIP_OPENCODE_PROVIDERS", + "ANTHROPIC_BASE_URL", + "OPENAI_BASE_URL", + "XAI_BASE_URL", + "CLAUDE_CODE_USE_BEDROCK", + "CLAUDE_CODE_USE_VERTEX", + "CLAUDE_CODE_USE_FOUNDRY", +] as const; +export function stripAiAuthBindings(env: unknown): Record { + const result = { + ...(env && typeof env === "object" ? (env as Record) : {}), + }; + for (const key of AI_AUTH_ENV_KEYS) + if ( + ![ + "ANTHROPIC_BASE_URL", + "OPENAI_BASE_URL", + "XAI_BASE_URL", + "CLAUDE_CODE_USE_BEDROCK", + "CLAUDE_CODE_USE_VERTEX", + "CLAUDE_CODE_USE_FOUNDRY", + "PAPERCLIP_OPENCODE_PROVIDERS", + ].includes(key) + ) + delete result[key]; + return result; +} +export async function assertManagedAiProjectAuth( + config: Record, + provider: AiConnectionBinding["provider"], + target?: AdapterExecutionTarget | null, +) { + const extraArgs = [ + ...(Array.isArray(config.extraArgs) ? config.extraArgs : []), + ...(Array.isArray(config.args) ? config.args : []), + ]; + if ( + extraArgs.some( + (arg) => + typeof arg === "string" && + /^(--config|-c|--settings|--setting-sources|--api-key|--auth-token)(=|$)/.test( + arg, + ), + ) + ) { + throw unprocessable( + "Remove authentication/configuration overrides before selecting a managed AI connection", + { code: "ai_connection_incompatible" }, + ); + } + const files = + provider === "anthropic" + ? [".claude/settings.json", ".claude/settings.local.json"] + : provider === "openai" + ? [".codex/config.toml"] + : []; + const pattern = + "apiKeyHelper|ANTHROPIC_API_KEY|ANTHROPIC_AUTH_TOKEN|CLAUDE_CODE_OAUTH_TOKEN|OPENAI_API_KEY|model_provider[[:space:]]*=|env_key[[:space:]]*=|experimental_bearer_token|cli_auth_credentials_store"; + if (target?.kind === "remote" && files.length) { + // Only inspect for conflicting keys; never return configuration or credential values. + const result = await runAdapterExecutionTargetProcess( + `ai-auth-check-${Date.now()}`, + target, + "sh", + [ + "-c", + ` +directory=$1; pattern=$2; shift 2 +while :; do + for relative in "$@"; do + file="$directory/$relative" + if test -f "$file"; then + grep -Eq "$pattern" "$file" + result=$? + if test "$result" -eq 0; then exit 42; fi + if test "$result" -ne 1; then exit 43; fi + fi + done + parent=$(dirname "$directory") + if test "$parent" = "$directory"; then break; fi + directory=$parent +done`, + "ai-auth-check", + target.remoteCwd, + pattern, + ...files, + ], + { + cwd: target.remoteCwd, + env: {}, + timeoutSec: 15, + graceSec: 1, + onLog: async () => {}, + }, + ); + if (result.exitCode !== 0) + throw unprocessable( + "The environment's project authentication settings must be checked before using this AI connection", + { code: "ai_connection_incompatible" }, + ); + return; + } + let directory = + typeof config.cwd === "string" ? path.resolve(config.cwd) : process.cwd(); + for (;;) { + for (const relative of files) { + try { + const content = await readFile(path.join(directory, relative), "utf8"); + if ( + /apiKeyHelper|ANTHROPIC_API_KEY|ANTHROPIC_AUTH_TOKEN|CLAUDE_CODE_OAUTH_TOKEN|OPENAI_API_KEY|model_provider\s*=|env_key\s*=|experimental_bearer_token|cli_auth_credentials_store/.test( + content, + ) + ) { + throw unprocessable( + "Project authentication settings conflict with the selected AI connection", + { code: "ai_connection_incompatible" }, + ); + } + } catch (error) { + if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error; + } + } + const parent = path.dirname(directory); + if (parent === directory) break; + directory = parent; + } +} + +async function acquireCredentialLease(db: Db, grantId: string) { + const client = await db.$client.reserve(); + try { + const [result] = + await client`select pg_try_advisory_lock(hashtextextended(${`ai-runtime:${grantId}`}, 0)) as acquired`; + if (!result.acquired) + throw unprocessable( + "This subscription is in use. Retry when its current execution finishes.", + { code: "ai_connection_busy" }, + ); + } catch (error) { + client.release(); + throw error; + } + let released = false; + return async () => { + if (released) return; + released = true; + try { + await client`select pg_advisory_unlock(hashtextextended(${`ai-runtime:${grantId}`}, 0))`; + } finally { + client.release(); + } + }; +} + +export async function prepareManagedAiRuntime( + db: Db, + input: { + companyId: string; + agentId: string; + responsibleUserId: string | null; + adapterType: string; + binding: AiConnectionBinding; + allowUninstalledPersonal?: boolean; + allowUninstalledShared?: boolean; + allowLegacyValidation?: boolean; + config: Record; + }, +) { + const configuredEnv = + input.config.env && typeof input.config.env === "object" + ? (input.config.env as Record) + : {}; + for (const key of [ + "ANTHROPIC_BASE_URL", + "OPENAI_BASE_URL", + "XAI_BASE_URL", + "CLAUDE_CODE_USE_BEDROCK", + "CLAUDE_CODE_USE_VERTEX", + "CLAUDE_CODE_USE_FOUNDRY", + "PAPERCLIP_OPENCODE_PROVIDERS", + ]) { + if (configuredEnv[key]) + throw unprocessable( + "The configured provider routing is incompatible with this AI connection", + { code: "ai_connection_incompatible" }, + ); + } + await assertManagedAiProjectAuth(input.config, input.binding.provider); + const service = aiConnectionService(db); + let selection = await service.select({ + ...input, + userId: input.responsibleUserId, + model: input.config.model, + runnerProvider: input.config.provider, + acpxAgent: input.config.acpxAgent, + }); + const release = + input.binding.method === "subscription" + ? await acquireCredentialLease(db, selection.grant.id) + : async () => {}; + let home: string | undefined; + try { + const selectedGrantId = selection.grant.id; + selection = await service.select({ + ...input, + userId: input.responsibleUserId, + model: input.config.model, + runnerProvider: input.config.provider, + acpxAgent: input.config.acpxAgent, + }); + if (selection.grant.id !== selectedGrantId) + throw unprocessable( + "The selected default changed. Retry this execution.", + ); + const value = await service.credential(selection); + home = await mkdtemp( + path.join( + os.tmpdir(), + `paperclip-ai-${input.companyId}-${selection.grant.id}-`, + ), + ); + const providerHome = path.join(home, "provider"); + await mkdir(providerHome, { mode: 0o700 }); + const env: Record = { + ...stripAiAuthBindings(input.config.env), + ...Object.fromEntries(AI_AUTH_ENV_KEYS.map((key) => [key, ""])), + HOME: home, + XDG_CONFIG_HOME: path.join(home, "config"), + XDG_DATA_HOME: path.join(home, "data"), + CODEX_HOME: providerHome, + GROK_HOME: providerHome, + CLAUDE_CONFIG_DIR: providerHome, + }; + const capability = + AI_CONNECTION_CAPABILITIES[input.binding.provider].methods[ + input.binding.method + ]!; + const authFile = path.join(providerHome, "auth.json"); + if (input.binding.provider === "openai") + await writeFile( + path.join(providerHome, "config.toml"), + 'cli_auth_credentials_store = "file"\n', + { mode: 0o600 }, + ); + const subscriptionFile = + input.binding.method === "subscription" && + input.binding.provider !== "anthropic"; + if (subscriptionFile) await writeFile(authFile, value, { mode: 0o600 }); + else env[capability.envKey] = value; + if ( + input.binding.provider === "openai" && + input.binding.method === "api_key" + ) { + env.CODEX_API_KEY = value; + await writeFile(authFile, JSON.stringify({ OPENAI_API_KEY: value }), { + mode: 0o600, + }); + } + if (input.binding.provider === "openrouter") { + env.OPENCODE_CONFIG_CONTENT = JSON.stringify({ + provider: { openrouter: { options: { apiKey: value } } }, + }); + env.OPENCODE_DISABLE_PROJECT_CONFIG = "true"; + } + const generation = createHash("sha256") + .update(value) + .digest("hex") + .slice(0, 16); + const identity = `${selection.grant.id}:${input.responsibleUserId ?? "shared"}:${generation}`; + return { + config: { + ...input.config, + env, + managedAiConnection: { ...selection.attribution, identity }, + }, + attribution: selection.attribution, + accountName: selection.connection.name, + accountOwnerUserId: selection.grant.subjectUserId, + identity, + cleanup: async () => { + try { + if (subscriptionFile) { + const refreshed = await readFile(authFile, "utf8"); + if (refreshed !== value) + await db.transaction(async (tx) => { + const [grant] = await tx + .select() + .from(connectionGrants) + .where( + and( + eq(connectionGrants.id, selection.grant.id), + eq(connectionGrants.companyId, input.companyId), + ), + ) + .for("update"); + // Reconnect/revocation wins over a process holding an older credential. + if ( + !grant || + grant.status !== "active" || + grant.updatedAt.getTime() !== + selection.grant.updatedAt.getTime() + ) + return; + const current = await service.credential({ + ...selection, + grant, + }); + const destination = path.join( + providerHome, + "current-auth.json", + ); + await writeFile(destination, current, { mode: 0o600 }); + const decision = + input.binding.provider === "openai" + ? await decideCodexAuthMerge(authFile, destination, { + errorLabel: "AI account refresh", + }) + : await decideGrokAuthMerge(authFile, destination, { + errorLabel: "AI account refresh", + }); + if (decision !== 10) return; + const ref = grant.credentialSecretRefs.find( + (r) => r.configPath === "ai.credential", + )!; + await secretService(tx).rotate( + ref.secretId, + { value: refreshed }, + { userId: grant.subjectUserId }, + ); + await tx + .update(connectionGrants) + .set({ updatedAt: new Date() }) + .where(eq(connectionGrants.id, grant.id)); + }); + } + } finally { + try { + if (home) await rm(home, { recursive: true, force: true }); + } finally { + await release(); + } + } + }, + }; + } catch (error) { + try { + if (home) await rm(home, { recursive: true, force: true }); + } finally { + await release(); + } + throw error; + } +} diff --git a/server/src/services/ai-connections.ts b/server/src/services/ai-connections.ts new file mode 100644 index 0000000000..c3a47d8729 --- /dev/null +++ b/server/src/services/ai-connections.ts @@ -0,0 +1,775 @@ +import { syncConnectionCredentialBindings } from "./connection-credential-bindings.js"; +import { randomUUID } from "node:crypto"; +import { and, eq, inArray, or } from "drizzle-orm"; +import { + type Db, + authUsers, + adapterAuthSessions, + aiConnectionDefaults, + agents, + companyMemberships, + companySecrets, + userSecretDefinitions, + connectionGrants, + connectionGrantMembers, + toolApplications, + toolConnections, + toolConnectionInstalls, +} from "@paperclipai/db"; +import { + AI_CONNECTION_CAPABILITIES, + aiConnectionMetadataSchema, + aiSubscriptionNeedsIsolatedLogin, + isAiConnectionCompatible, + type AiConnectionBinding, + type AiConnectionAttribution, + type AiConnectionMetadata, + type AiManagedConnectionSummary, + type CreateAiConnection, + type AiConnectionLoginIntent, +} from "@paperclipai/shared"; +import { forbidden, notFound, unprocessable } from "../errors.js"; +import { logActivity } from "./activity-log.js"; +import { secretService } from "./secrets.js"; + +/** Same human audience displayed by the existing Connections identity controls. */ +function canUseCredential( + grant: { kind: string; subjectUserId: string | null }, + userId: string | null, + audience: { subjectType: string; subjectId: string }[], +) { + if (!userId) return false; + if (grant.kind === "user") return grant.subjectUserId === userId; + return grant.kind === "organization" && ( + audience.length === 0 || audience.some((member) => member.subjectType === "user" && member.subjectId === userId) + ); +} + +export function aiConnectionService(db: Db) { + const secrets = secretService(db); + async function membership(companyId: string, userId: string | null) { + if (!userId) return false; + return Boolean( + ( + await db + .select({ id: companyMemberships.id }) + .from(companyMemberships) + .where( + and( + eq(companyMemberships.companyId, companyId), + eq(companyMemberships.principalType, "user"), + eq(companyMemberships.principalId, userId), + eq(companyMemberships.status, "active"), + ), + ) + .limit(1) + )[0], + ); + } + async function rows(companyId: string) { + return db + .select({ connection: toolConnections, grant: connectionGrants }) + .from(toolConnections) + .innerJoin( + connectionGrants, + and( + eq(connectionGrants.companyId, toolConnections.companyId), + eq(connectionGrants.connectionId, toolConnections.id), + ), + ) + .where( + and( + eq(toolConnections.companyId, companyId), + eq(toolConnections.connectionPurpose, "ai"), + ), + ); + } + async function list( + companyId: string, + userId: string, + _agentId?: string, + ): Promise { + const [accounts, defaults, members, owners] = + await Promise.all([ + rows(companyId), + db + .select() + .from(aiConnectionDefaults) + .where( + and( + eq(aiConnectionDefaults.companyId, companyId), + eq(aiConnectionDefaults.userId, userId), + ), + ), + db + .select() + .from(connectionGrantMembers) + .where(eq(connectionGrantMembers.companyId, companyId)), + db + .select({ id: authUsers.id, name: authUsers.name }) + .from(authUsers) + .innerJoin( + companyMemberships, + and( + eq(companyMemberships.principalId, authUsers.id), + eq(companyMemberships.companyId, companyId), + eq(companyMemberships.principalType, "user"), + ), + ), + ]); + return accounts.flatMap(({ connection, grant }) => { + const metadata = aiConnectionMetadataSchema.safeParse( + connection.config.ai, + ); + if (!metadata.success) return []; + const needsReconnect = aiSubscriptionNeedsIsolatedLogin(connection.config); + if (!canUseCredential(grant, userId, members.filter((m) => m.grantId === grant.id))) + return []; + return [ + { + id: connection.id, + grantId: grant.id, + companyId, + ...metadata.data, + name: connection.name, + accountLabel: grant.providerTenant?.name, + ...(needsReconnect ? { unavailableReason: "Reconnect with a separate sign-in to protect your existing terminal login." } : {}), + ownership: + grant.kind === "user" ? ("personal" as const) : ("shared" as const), + ownerUserId: grant.subjectUserId ?? undefined, + ownerName: + owners.find((owner) => owner.id === grant.subjectUserId)?.name ?? + (grant.subjectUserId === userId ? "You" : "Account owner"), + isDefault: defaults.some((d) => d.grantId === grant.id), + status: + grant.status === "revoked" + ? ("revoked" as const) + : grant.status === "expired" + ? ("expired" as const) + : grant.status !== "active" || + !connection.enabled || + connection.status !== "active" || + connection.healthStatus !== "ok" || needsReconnect + ? ("needs_attention" as const) + : ("connected" as const), + }, + ]; + }); + } + async function setDefault( + companyId: string, + userId: string, + grantId: string, + ) { + return db.transaction(async (tx) => { + const [row] = await tx + .select({ connection: toolConnections, grant: connectionGrants }) + .from(connectionGrants) + .innerJoin( + toolConnections, + eq(toolConnections.id, connectionGrants.connectionId), + ) + .where( + and( + eq(connectionGrants.companyId, companyId), + eq(connectionGrants.id, grantId), + eq(connectionGrants.subjectUserId, userId), + eq(connectionGrants.kind, "user"), + ), + ) + .for("update"); + if (!row) + throw forbidden("Only the owner can choose their personal default"); + if ( + row.connection.connectionPurpose !== "ai" || + row.grant.status !== "active" || + !row.connection.enabled || + row.connection.healthStatus !== "ok" + ) + throw unprocessable( + "Reconnect this account before making it your default", + ); + const metadata = aiConnectionMetadataSchema.parse( + row.connection.config.ai, + ); + await tx + .insert(aiConnectionDefaults) + .values({ companyId, userId, ...metadata, grantId }) + .onConflictDoUpdate({ + target: [ + aiConnectionDefaults.companyId, + aiConnectionDefaults.userId, + aiConnectionDefaults.provider, + aiConnectionDefaults.method, + ], + set: { grantId, updatedAt: new Date() }, + }); + }); + } + async function select(input: { + companyId: string; + userId: string | null; + agentId: string; + adapterType: string; + model?: unknown; + runnerProvider?: unknown; + acpxAgent?: unknown; + allowUninstalledPersonal?: boolean; + allowUninstalledShared?: boolean; + allowLegacyValidation?: boolean; + binding: AiConnectionBinding; + }) { + const { companyId, userId, agentId, binding } = input; + if ( + !isAiConnectionCompatible( + binding, + input.adapterType, + input.model, + input.runnerProvider, + input.acpxAgent, + ) + ) + throw unprocessable( + "Select an AI connection compatible with this harness and model", + { code: "ai_connection_incompatible" }, + ); + if (binding.mode === "responsible_user" && !userId) + throw unprocessable( + "This run needs a responsible user to select an AI connection", + { code: "ai_connection_responsible_user_missing" }, + ); + if (userId && !(await membership(companyId, userId))) + throw forbidden("The responsible user is not an active company member"); + const defaultRow = + binding.mode === "responsible_user" + ? ( + await db + .select() + .from(aiConnectionDefaults) + .where( + and( + eq(aiConnectionDefaults.companyId, companyId), + eq(aiConnectionDefaults.userId, userId!), + eq(aiConnectionDefaults.provider, binding.provider), + eq(aiConnectionDefaults.method, binding.method), + ), + ) + .limit(1) + )[0] + : null; + const grantId = + binding.mode === "responsible_user" + ? defaultRow?.grantId + : binding.grantId; + if (!grantId) + throw unprocessable( + "Connect an account and choose your personal default", + { code: "ai_connection_default_missing" }, + ); + const row = (await rows(companyId)).find( + (r) => + r.grant.id === grantId && + (binding.mode === "responsible_user" || + r.connection.id === binding.connectionId), + ); + if (!row) + throw unprocessable("The selected AI connection is unavailable", { + code: "ai_connection_missing", + }); + const { connection, grant } = row; + const metadata = aiConnectionMetadataSchema.safeParse(connection.config.ai); + if ( + !metadata.success || + metadata.data.provider !== binding.provider || + metadata.data.method !== binding.method + ) + throw unprocessable("The selected AI connection is incompatible", { + code: "ai_connection_incompatible", + }); + if (aiSubscriptionNeedsIsolatedLogin(connection.config)) + throw unprocessable("Reconnect this subscription with a separate sign-in to protect your existing terminal login.", { + code: "ai_connection_unavailable", connectionId: connection.id, + }); + if ( + grant.status !== "active" || + !connection.enabled || + connection.status !== "active" || + (connection.healthStatus !== "ok" && + !( + input.allowLegacyValidation && + connection.config.aiLegacyAdoption === true + )) + ) + throw unprocessable("Reconnect or validate the selected AI account", { + code: "ai_connection_unavailable", + connectionId: connection.id, + }); + if ( + grant.kind === "user" && + !(await membership(companyId, grant.subjectUserId)) + ) + throw forbidden( + "The account owner is no longer an active company member", + ); + if ( + binding.mode === "responsible_user" && + (grant.kind !== "user" || grant.subjectUserId !== userId) + ) + throw forbidden("The default must belong to the responsible user"); + if (binding.mode === "shared" && grant.kind !== "organization") + throw forbidden("Select a company-shared account"); + if (binding.mode === "delegated" && grant.kind !== "user") + throw forbidden("Select a personal account"); + const audience = await db + .select() + .from(connectionGrantMembers) + .where(and( + eq(connectionGrantMembers.companyId, companyId), + eq(connectionGrantMembers.grantId, grant.id), + )); + // The existing human-access permission is authoritative for every binding, + // including old explicit personal selections. Agent delegation cannot bypass it. + if (!canUseCredential(grant, userId, audience)) + throw forbidden("This credential is not shared with the responsible user"); + const installs = await db + .select() + .from(toolConnectionInstalls) + .where( + and( + eq(toolConnectionInstalls.companyId, companyId), + eq(toolConnectionInstalls.connectionId, connection.id), + or( + and( + eq(toolConnectionInstalls.targetType, "company"), + eq(toolConnectionInstalls.targetId, companyId), + ), + and( + eq(toolConnectionInstalls.targetType, "agent"), + eq(toolConnectionInstalls.targetId, agentId), + ), + ), + ), + ); + if ( + !installs.length && + !( + (input.allowUninstalledPersonal && + binding.mode === "responsible_user" && grant.subjectUserId === userId) || + (input.allowUninstalledShared && binding.mode === "shared" && grant.kind === "organization") + ) + ) + throw forbidden("This connection is not permitted for this agent"); + return { + ...row, + attribution: { + connectionId: connection.id, + grantId: grant.id, + provider: binding.provider, + method: binding.method, + mode: binding.mode, + responsibleUserId: userId, + } satisfies AiConnectionAttribution, + }; + } + async function credential(row: Awaited>) { + const ref = row.grant.credentialSecretRefs.find( + (r) => r.configPath === "ai.credential", + ); + if (!ref) + throw unprocessable("Reconnect this AI account", { + code: "ai_connection_credential_missing", + }); + const [secret] = await db + .select() + .from(companySecrets) + .where( + and( + eq(companySecrets.id, ref.secretId), + eq(companySecrets.companyId, row.connection.companyId), + ), + ); + if (!secret) + throw unprocessable("Reconnect this AI account", { + code: "ai_connection_credential_missing", + }); + const context = { + consumerType: "tool_connection" as const, + consumerId: row.connection.id, + configPath: ref.configPath, + responsibleUserId: row.grant.subjectUserId, + actorType: "system" as const, + }; + if (secret.scope === "user") { + if ( + secret.ownerUserId !== row.grant.subjectUserId || + !secret.userSecretDefinitionId + ) + throw forbidden("Credential ownership mismatch"); + const result = await secrets.resolveUserSecretValue( + row.connection.companyId, + { + definitionId: secret.userSecretDefinitionId, + responsibleUserId: secret.ownerUserId, + required: true, + version: "latest", + }, + context, + ); + if (!result) throw unprocessable("Reconnect this AI account"); + return result.value; + } + return secrets.resolveSecretValue( + row.connection.companyId, + secret.id, + "latest", + context, + ); + } + async function save( + companyId: string, + userId: string, + input: CreateAiConnection | AiConnectionLoginIntent, + verifiedCredential: string, + sessionId?: string, + attemptStartedAt = new Date(), + ) { + if (!(await membership(companyId, userId))) + throw forbidden("An active company member must own this connection"); + const reconnect = input.connectionId + ? (await rows(companyId)).find( + (r) => r.connection.id === input.connectionId, + ) + : undefined; + if (input.connectionId && !reconnect) + throw notFound("AI connection not found"); + if ( + reconnect && + (reconnect.grant.createdByUserId !== userId || + (reconnect.grant.kind === "user" && + reconnect.grant.subjectUserId !== userId)) + ) + throw forbidden("Only the account owner can reconnect it"); + if ( + reconnect && + (reconnect.connection.config.ai as AiConnectionMetadata).provider !== + input.provider + ) + throw unprocessable("Reconnect cannot change providers"); + if ( + reconnect && + ((reconnect.connection.config.ai as AiConnectionMetadata).method !== + input.method || + (reconnect.grant.kind === "user") !== (input.ownership === "personal")) + ) + throw unprocessable( + "Reconnect cannot change the sign-in method or ownership", + ); + const id = reconnect?.connection.id ?? randomUUID(); + const grantId = reconnect?.grant.id ?? randomUUID(); + return db.transaction(async (tx) => { + const secrets = secretService(tx); + if (sessionId) { + const [session] = await tx + .select() + .from(adapterAuthSessions) + .where( + and( + input.provider === "anthropic" + ? eq(adapterAuthSessions.publicSessionId, sessionId) + : eq(adapterAuthSessions.id, sessionId), + eq(adapterAuthSessions.companyId, companyId), + eq(adapterAuthSessions.startedByUserId, userId), + ), + ) + .for("update"); + if (!session) throw forbidden("Login session ownership mismatch"); + attemptStartedAt = session.createdAt; + + if (session.connectionId && session.connectionGrantId) + return { + connectionId: session.connectionId, + grantId: session.connectionGrantId, + }; + if ( + !["promoting", "submitting", "awaiting_code"].includes( + session.status, + ) || + (session.expiresAt && session.expiresAt.getTime() <= Date.now()) + ) + throw unprocessable("The login attempt is no longer active"); + if ( + !session.aiConnection || + session.aiConnection.provider !== input.provider || + session.aiConnection.method !== input.method || + session.aiConnection.connectionId !== input.connectionId || + session.aiConnection.ownership !== input.ownership || + session.aiConnection.allAgents !== input.allAgents || + JSON.stringify(session.aiConnection.agentIds) !== + JSON.stringify(input.agentIds) + ) + throw forbidden("Login target mismatch"); + } + if (reconnect) { + const [current] = await tx + .select() + .from(connectionGrants) + .where(eq(connectionGrants.id, grantId)) + .for("update"); + if ( + !current || + current.updatedAt.getTime() !== reconnect.grant.updatedAt.getTime() || + current.updatedAt.getTime() > attemptStartedAt.getTime() + ) + throw unprocessable("The connection changed. Start reconnect again."); + } + let secretId = reconnect?.grant.credentialSecretRefs.find( + (r) => r.configPath === "ai.credential", + )?.secretId; + // Adoption indexes existing credentials without transferring ownership. + // Only rotate the private slot created for this grant. A reconnect of an + // indexed credential must leave every legacy consumer's value untouched, + // even after adoption has cleared the connection's validation marker. + if (secretId) { + const [source] = await tx.select({ name: companySecrets.name, key: userSecretDefinitions.key }) + .from(companySecrets) + .leftJoin(userSecretDefinitions, eq(userSecretDefinitions.id, companySecrets.userSecretDefinitionId)) + .where(and(eq(companySecrets.companyId, companyId), eq(companySecrets.id, secretId))); + const privateSlot = input.ownership === "personal" + ? source?.key === `ai_${grantId.replaceAll("-", "_")}` + : source?.name === `ai-${grantId}`; + if (!privateSlot) secretId = undefined; + } + if (secretId) + await secrets.rotate( + secretId, + { value: verifiedCredential }, + { userId }, + ); + else if (input.ownership === "personal") { + const definition = await secrets.createUserSecretDefinition( + companyId, + { + key: `ai_${grantId.replaceAll("-", "_")}`, + name: input.name, + provider: "local_encrypted", + }, + { userId }, + ); + const secret = await secrets.createCurrentUserSecretValue( + companyId, + userId, + { definitionId: definition.id, value: verifiedCredential }, + { userId }, + ); + secretId = secret.id; + } else + secretId = ( + await secrets.create( + companyId, + { + name: `ai-${grantId}`, + provider: "local_encrypted", + value: verifiedCredential, + }, + { userId }, + ) + ).id; + if (input.agentIds.length) { + const targets = await tx + .select({ id: agents.id }) + .from(agents) + .where( + and( + eq(agents.companyId, companyId), + inArray(agents.id, input.agentIds), + ), + ); + if (targets.length !== new Set(input.agentIds).size) + throw forbidden("Agent does not belong to this company"); + } + const key = `app-gallery:${input.provider}`; + await tx + .insert(toolApplications) + .values({ + companyId, + applicationKey: key, + name: AI_CONNECTION_CAPABILITIES[input.provider].name, + type: "mcp_http", + metadata: { sourceTemplateKey: input.provider }, + ownerUserId: userId, + }) + .onConflictDoNothing(); + const [app] = await tx + .select() + .from(toolApplications) + .where( + and( + eq(toolApplications.companyId, companyId), + or( + eq(toolApplications.applicationKey, key), + eq( + toolApplications.name, + AI_CONNECTION_CAPABILITIES[input.provider].name, + ), + ), + ), + ); + if (!app) throw unprocessable("Could not find the provider application"); + if (reconnect) + await tx + .update(toolConnections) + .set({ + enabled: true, + status: "active", + healthStatus: "ok", + healthMessage: null, + config: { ...reconnect.connection.config, aiIsolatedSubscription: input.method === "subscription" && input.provider !== "anthropic" }, + updatedAt: new Date(), + }) + .where(eq(toolConnections.id, id)); + else + await tx + .insert(toolConnections) + .values({ + id, + companyId, + applicationId: app.id, + name: input.name, + uid: `ai-${id}`, + connectionPurpose: "ai", + transport: "runtime_auth", + authKind: input.method === "api_key" ? "api_key" : "oauth", + credentialPolicy: + input.ownership === "personal" ? "per_user" : "shared", + status: "active", + enabled: true, + healthStatus: "ok", + config: { + sourceTemplateKey: input.provider, + ai: { provider: input.provider, method: input.method }, + aiIsolatedSubscription: input.method === "subscription" && input.provider !== "anthropic", + }, + createdByUserId: userId, + }); + let accountLabel: string | undefined; + if (input.method === "subscription" && input.provider !== "anthropic") { + try { + const credential = JSON.parse(verifiedCredential); + const claims = credential.tokens?.id_token + ? JSON.parse( + Buffer.from( + credential.tokens.id_token.split(".")[1], + "base64url", + ).toString(), + ) + : credential; + const email = claims.email ?? claims.user?.email; + if ( + typeof email === "string" && + /^[^\s@/\\]{1,100}@[^\s@/\\]{1,100}\.[^\s@/\\]{2,40}$/.test(email) + ) + accountLabel = email; + } catch { + /* Safe account identity is optional. */ + } + } + const refs = [ + { + secretId: secretId!, + configPath: "ai.credential", + required: true, + versionSelector: "latest" as const, + }, + ]; + if (reconnect) + await tx + .update(connectionGrants) + .set({ + status: "active", + providerTenant: accountLabel ? { name: accountLabel } : {}, + credentialSecretRefs: refs, + revokedAt: null, + updatedAt: new Date(), + }) + .where(eq(connectionGrants.id, grantId)); + else + await tx + .insert(connectionGrants) + .values({ + id: grantId, + companyId, + connectionId: id, + kind: input.ownership === "personal" ? "user" : "organization", + subjectUserId: input.ownership === "personal" ? userId : null, + isDefault: input.ownership === "shared", + providerTenant: accountLabel ? { name: accountLabel } : {}, + credentialSecretRefs: refs, + createdByUserId: userId, + }); + const [savedConnection] = await tx + .select() + .from(toolConnections) + .where(eq(toolConnections.id, id)); + await syncConnectionCredentialBindings(tx, savedConnection, refs); + if (input.ownership === "personal") + await tx + .insert(aiConnectionDefaults) + .values({ + companyId, + userId, + provider: input.provider, + method: input.method, + grantId, + }) + .onConflictDoNothing(); + if (!reconnect) { + const installs = input.allAgents + ? [{ targetType: "company" as const, targetId: companyId }] + : input.agentIds.map((targetId) => ({ + targetType: "agent" as const, + targetId, + })); + if (installs.length) + await tx + .insert(toolConnectionInstalls) + .values( + installs.map((i) => ({ + ...i, + companyId, + connectionId: id, + createdByUserId: userId, + })), + ); + } + if (sessionId) + await tx + .update(adapterAuthSessions) + .set({ + connectionId: id, + connectionGrantId: grantId, + connectionMethod: input.method, + ...(input.provider === "anthropic" + ? { status: "stored" as const } + : {}), + }) + .where( + input.provider === "anthropic" + ? eq(adapterAuthSessions.publicSessionId, sessionId) + : eq(adapterAuthSessions.id, sessionId), + ); + await logActivity(tx as unknown as Db, { + companyId, + actorType: "user", + actorId: userId, + action: reconnect + ? "ai_connection.reconnected" + : "ai_connection.connected", + entityType: "tool_connection", + entityId: id, + details: { provider: input.provider, method: input.method, grantId }, + }); + return { connectionId: id, grantId }; + }); + } + return { list, select, credential, save, setDefault, membership }; +} diff --git a/server/src/services/authorization.ts b/server/src/services/authorization.ts index a63b1a407c..70b1d2032c 100644 --- a/server/src/services/authorization.ts +++ b/server/src/services/authorization.ts @@ -1784,6 +1784,7 @@ export function authorizationService(db: Db | DbTransaction) { } if ( input.action === "agent:read" || + input.action === "agent:wake" || input.action === "company_scope:read" || input.action === "decision_queue:manage" || input.action === "decision_queue:read" || @@ -1798,6 +1799,7 @@ export function authorizationService(db: Db | DbTransaction) { // Mirroring the tasks:assign carve-out above, viewers keep the // read-only visibility actions but not the privileged ones. const requiresNonViewer = + input.action === "agent:wake" || input.action === "runtime:manage" || input.action === "secrets:read" || input.action === "decision_queue:manage" || diff --git a/server/src/services/chat-channels.ts b/server/src/services/chat-channels.ts index 605466842d..a8531e21df 100644 --- a/server/src/services/chat-channels.ts +++ b/server/src/services/chat-channels.ts @@ -1,3 +1,16 @@ +import { takePhotonCompanion } from "./photon/attachments.js"; +import { writePhotonCheckpoint } from "./photon/receiver.js"; +import { PhotonState } from "./photon/state.js"; +import { nativeSha256 } from "./native-runtime/canonical.js"; +import { HEIF_CONTENT_TYPES, photonHeifPreview, validatePhotonImage } from "./photon/media.js"; +import { projectSafeChatPublicationText } from "./chat-publication-projection.js"; +import { PhotonAnswerValidationError, nativePhotonInteraction, publishPhotonPrompt, photonResponseCommand, parsePhotonQuestionAnswer, type PhotonPromptReceipt, type PhotonInteractionBinding, type PhotonDraft } from "./photon/interactions.js"; +import { validateNativeQuestionResponseInput } from "./native-runtime/native-question-bridge.js"; +import type { AskUserQuestionsAnswer, AskUserQuestionsInteraction, IssueThreadInteraction } from "@paperclipai/shared"; +import { PhotonCloudClient, PhotonError, photonFailure, photonSharedIdentity, photonSharedScope } from "./photon/cloud.js"; +import { PhotonChatAdapter, photonThreadId, photonReplyReference } from "./photon/adapter.js"; +import { photonChannelConfigurationSchema, type PhotonChannelConfiguration } from "@paperclipai/shared"; +import type { LiveEvent as PhotonEvent } from "@photon-ai/advanced-imessage"; import { createHash, createHmac, @@ -383,6 +396,7 @@ function publicationSummary( } const PROVIDER_LABELS: Record = { + "imessage-photon": "iMessage Photon", agentmail: "AgentMail", slack: "Slack", github: "GitHub", @@ -577,6 +591,7 @@ async function inspectSlackCallback( } const CAPABILITIES: Record = { + "imessage-photon": { threads: false, directMessages: true, nativeStreaming: false, messageEdits: true, messageDeletes: false, reactions: false, files: true, cards: false, actions: true, modals: false, slashCommands: false, ephemeralMessages: false, proactiveDirectMessages: false }, agentmail: { threads: true, directMessages: true, nativeStreaming: false, messageEdits: false, messageDeletes: false, reactions: false, files: true, cards: false, actions: false, modals: false, slashCommands: false, ephemeralMessages: false, proactiveDirectMessages: true }, slack: { threads: true, @@ -670,6 +685,7 @@ const REQUIRED_CREDENTIALS: Record< Exclude, readonly string[] > = { + "imessage-photon": ["projectSecret"], agentmail: [], slack: ["botToken", "signingSecret"], discord: ["botToken", "applicationId", "guildId"], @@ -728,6 +744,7 @@ const SUPPORTED_GITHUB_WEBHOOK_EVENTS = new Set([ ]); const SUPPLIED_CREDENTIAL_KEYS: Record = { + "imessage-photon": ["projectSecret"], agentmail: [], slack: ["botToken", "signingSecret"], github: ["appId", "privateKey"], @@ -767,6 +784,7 @@ const PUBLICATION_ENDPOINT_CONCURRENCY = 4; const CREDENTIAL_MUTATION_LEASE_WAIT_MS = 10_000; const CREDENTIAL_MUTATION_LEASE_POLL_MS = 25; const DISCORD_GATEWAY_LEASE_KEY = "discord_gateway_runtime"; +function leasedChatProvider(provider: string): boolean { return provider === "discord" || provider === "imessage-photon"; } const DISCORD_GATEWAY_LEASE_TTL_MS = 15_000; const DISCORD_GATEWAY_LEASE_WAIT_MS = 20_000; const DISCORD_GATEWAY_LEASE_POLL_MS = 100; @@ -801,6 +819,7 @@ type DbOrTransaction = Db | DbTransaction; type SlackCallbackSurface = keyof ChatEndpointCallbackSurfaces; type SlackCallbackObservation = { url: string; observedAt: string }; type InternalSetupState = ChatEndpointSetupState & { + photonIntakeAfter?: string; runtimeGeneration?: number; slackCallbackSurfaces?: Partial< Record @@ -1469,7 +1488,7 @@ type DiscordGatewayOwnership = { context: RuntimeContext; endpointId: string; expiresAt: Date; - leaseKey: typeof DISCORD_GATEWAY_LEASE_KEY; + leaseKey: typeof DISCORD_GATEWAY_LEASE_KEY | "photon_receiver_runtime"; renewTimer: ReturnType | null; renewal: Promise | null; stopPromise: Promise | null; @@ -1487,6 +1506,7 @@ function providerResourceType( ): string { if (surfaceKind === "direct_message") return "direct_message"; if (provider === "github") return "repository"; + if (provider === "imessage-photon") return "group_chat"; if (provider === "discord") return "channel"; if (provider === "microsoft-teams") return surfaceKind === "linear_group" ? "group_chat" : "channel"; @@ -1605,6 +1625,7 @@ function chatSurfaceKind( thread: Thread, ): ChatSurfaceKind { if (thread.isDM) return "direct_message"; + if (provider === "imessage-photon") return "linear_group"; if (provider === "telegram") { return /^telegram:[^:]+:[^:]+$/.test(thread.id) ? "native_thread" @@ -2594,6 +2615,7 @@ function providerSetupState( const webhookUrl = publicBaseUrl ? `${publicBaseUrl}${path}` : null; const step = endpoint.status === "active" ? "complete" : endpoint.setup.step; switch (endpoint.provider) { + case "imessage-photon": return { step, providerUrl: "https://photon.codes/", testStartedAt: endpoint.setup.testStartedAt } as const; case "agentmail": return endpoint.setup; case "slack": { const observations = (endpoint.setup as InternalSetupState) @@ -3083,7 +3105,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { }, ["verifying", "active", "attention"], ); - if (!current || current.provider !== "discord") return false; + if (!current || !leasedChatProvider(current.provider)) return false; const expiresAt = new Date( now.getTime() + (options.discordGatewayLeaseTtlMs ?? DISCORD_GATEWAY_LEASE_TTL_MS), @@ -3231,6 +3253,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { context: RuntimeContext, waitForOwnership: boolean, ): Promise { + const receiverLeaseKey = endpoint.provider === "imessage-photon" ? "photon_receiver_runtime" : DISCORD_GATEWAY_LEASE_KEY; const local = discordGatewayOwnerships.get(endpoint.id); if ( local && @@ -3260,7 +3283,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { .values({ companyId: endpoint.companyId, endpointId: endpoint.id, - leaseKey: DISCORD_GATEWAY_LEASE_KEY, + leaseKey: receiverLeaseKey, token, expiresAt, }) @@ -3275,7 +3298,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { and( eq(chatEndpointLeases.companyId, endpoint.companyId), eq(chatEndpointLeases.endpointId, endpoint.id), - eq(chatEndpointLeases.leaseKey, DISCORD_GATEWAY_LEASE_KEY), + eq(chatEndpointLeases.leaseKey, receiverLeaseKey), lte(chatEndpointLeases.expiresAt, now), ), ) @@ -3286,7 +3309,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { context, endpointId: endpoint.id, expiresAt, - leaseKey: DISCORD_GATEWAY_LEASE_KEY, + leaseKey: receiverLeaseKey, renewal: null, renewTimer: null, stopPromise: null, @@ -3323,6 +3346,9 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { } async function invalidateRuntime(endpointId: string): Promise { + for (const [deliveryId, live] of liveInboundMessages) { + if (live.runtimeContext?.endpointRuntime === runtime.get(endpointId) && live.thread.id.startsWith("imessage-photon:")) liveInboundMessages.delete(deliveryId); + } runtimeLocalEpochs.set(endpointId, localRuntimeEpoch(endpointId) + 1); runtimeVersions.delete(endpointId); const discordOwnership = discordGatewayOwnerships.get(endpointId); @@ -5232,6 +5258,13 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { sent = await target.post(payload.fallbackText); } } + } else if (claim.endpoint.provider === "imessage-photon") { + const adapter = (await runtimeFor(claim.endpoint)).getProviderAdapter(); + if (!(adapter instanceof PhotonChatAdapter)) throw new Error("Photon runtime unavailable"); + const receipt = await adapter.publish(payload.threadId, `effect:${action.id}`, projectSafeChatPublicationText(payload.text), { + assertCurrent: async () => { await credentialLease.assertOwned(); }, + }); + sent = { id: receipt.id, threadId: payload.threadId }; } else { sent = await target.post(payload.text); } @@ -5460,7 +5493,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { } const runtimeContext = context as RuntimeContext; if ( - endpoint.provider === "discord" && + leasedChatProvider(endpoint.provider) && runtimeContext.discordGatewayOwned === true && runtimeContext.endpointRuntime !== undefined && !discordGatewayRuntimeIsCurrent(endpointId, runtimeContext) @@ -5477,7 +5510,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { ) { const record = await endpointRecord(endpointId); if ( - record?.endpoint.provider === "discord" && + record && leasedChatProvider(record.endpoint.provider) && !(await ensureDiscordGatewayRuntimeIsCurrent(endpointId, context)) ) { return null; @@ -5488,7 +5521,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { credentialFingerprint(record.credentialSecretRefs) === context.credentialFingerprint && runtime.get(endpointId) === context.endpointRuntime && - (record.endpoint.provider !== "discord" || + (!leasedChatProvider(record.endpoint.provider) || discordGatewayRuntimeIsCurrent(endpointId, context)) ? record : null; @@ -5767,6 +5800,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { botUsername: endpoint.botUsername, botLabel: endpoint.botDisplayName ?? row.assignedAgentName, botAvatarUrl: endpoint.botAvatarUrl, + ...(endpoint.provider === "imessage-photon" && endpoint.botExternalId ? { photonAllocation: endpoint.botExternalId.startsWith("photon-project:") ? "shared" as const : "dedicated" as const } : {}), allowDirectMessages: endpoint.allowDirectMessages, allowGroupChats: endpoint.allowGroupChats, allowUnlinkedPeople: endpoint.allowUnlinkedPeople, @@ -5944,6 +5978,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { // enables that surface, even when this process is running against a // database created before the column default was hardened. allowGroupChats: input.provider !== "microsoft-teams", + allowUnlinkedPeople: input.provider !== "imessage-photon", capabilities: CAPABILITIES[input.provider], setup: { step: "provider_setup", @@ -5990,6 +6025,8 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { }; if (input.allowDirectMessages !== undefined) values.allowDirectMessages = input.allowDirectMessages; + if (input.allowGroupChats && existing.endpoint.provider === "imessage-photon" && (!existing.endpoint.botExternalId || existing.endpoint.botExternalId.startsWith("photon-project:"))) + throw unprocessable("Photon shared channels support direct messages only; groups require a dedicated channel"); if (input.allowGroupChats !== undefined) values.allowGroupChats = input.allowGroupChats; if (input.allowUnlinkedPeople !== undefined) @@ -6049,6 +6086,17 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { provider: ChatProvider, credentials: Record, ): Promise { + if (provider === "imessage-photon") { + const inspection = await inspectPhotonCredentials(credentials.projectId, credentials.projectSecret); + if (inspection.allocation !== (credentials.allocation ?? "dedicated")) throw unprocessable("Photon allocation changed; inspect the project again"); + if (inspection.allocation === "shared") { + if (!inspection.eligible) throw unprocessable("Photon shared project is unavailable"); + return { providerAccountId: inspection.projectId, providerAccountLabel: inspection.projectName, botExternalId: photonSharedIdentity(inspection.projectId), botUsername: null, botLabel: `${inspection.projectName} (DM only)` }; + } + const line = inspection.lines.find((candidate) => candidate.lineId === credentials.lineId && candidate.eligible); + if (!line || !inspection.eligible) throw unprocessable("Select an eligible dedicated Photon line"); + return { providerAccountId: inspection.projectId, providerAccountLabel: inspection.projectName, botExternalId: line.phoneNumber, botUsername: line.phoneNumber, botLabel: line.phoneNumber }; + } if (provider === "slack") { const response = await fetchImpl("https://slack.com/api/auth.test", { method: "POST", @@ -6273,7 +6321,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { return ( provider === "github" || provider === "discord" || - provider === "microsoft-teams" + provider === "microsoft-teams" || provider === "imessage-photon" ); } @@ -6330,7 +6378,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { : eq(chatEndpoints.companyId, endpoint.companyId), eq(chatEndpoints.provider, endpoint.provider), ne(chatEndpoints.id, endpoint.id), - inArray(chatEndpoints.status, [ + endpoint.provider === "imessage-photon" ? ne(chatEndpoints.status, "archived") : inArray(chatEndpoints.status, [ "verifying", "active", "paused", @@ -6369,6 +6417,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { function isNativeBotIdentityUniqueViolation(error: unknown): boolean { return ( + isUniqueViolation(error, "chat_endpoints_photon_number_uq") || isUniqueViolation(error, "chat_endpoints_live_bot_external_uq") || isUniqueViolation(error, "chat_endpoints_live_discord_bot_external_uq") || isUniqueViolation( @@ -6406,6 +6455,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { const refs: ToolCredentialSecretRef[] = []; try { for (const [key, value] of Object.entries(credentials)) { + if (endpoint.provider === "imessage-photon" && key !== "projectSecret") continue; await credentialLease.assertOwned(); const suffix = randomUUID(); const secret = await secrets.create( @@ -6601,7 +6651,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { endpoint: EndpointRow, ): Promise> { const connection = await db - .select({ refs: toolConnections.credentialSecretRefs }) + .select({ refs: toolConnections.credentialSecretRefs, config: toolConnections.config }) .from(toolConnections) .where( and( @@ -6611,7 +6661,12 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { ) .then((rows) => rows[0] ?? null); if (!connection) throw notFound("Chat connection not found"); - return resolveCredentialRefs(endpoint, connection.refs); + const values = await resolveCredentialRefs(endpoint, connection.refs); + if (endpoint.provider === "imessage-photon") { + const configuration = photonChannelConfigurationSchema.parse(connection.config.photon); + return { ...values, ...configuration, lineId: configuration.allocation === "shared" ? photonSharedScope(configuration.projectId) : configuration.lineId }; + } + return values; } async function acquireCredentialMutationLease(endpoint: EndpointRow) { @@ -7444,6 +7499,11 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { userName: string, credentials: Record, ): ResolvedChatSdkProviderConfig { + if (endpoint.provider === "imessage-photon") return { + provider: "imessage-photon", userName, + intakeAfter: Date.parse(String((endpoint.setup as InternalSetupState).photonIntakeAfter ?? endpoint.setup.testStartedAt ?? endpoint.createdAt.toISOString())), + credentials: { allocation: credentials.allocation === "shared" ? "shared" : "dedicated", projectId: credentials.projectId, lineId: credentials.lineId, projectSecret: credentials.projectSecret, phoneNumber: endpoint.botExternalId! }, + }; if (endpoint.provider === "slack") return { provider: "slack", @@ -7689,7 +7749,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { if ( current && runtimeVersions.get(endpoint.id) === context.version && - (record.endpoint.provider !== "discord" || + (!leasedChatProvider(record.endpoint.provider) || optionsForRuntime.requireDiscordOwnership !== true || currentOwnsDiscordGateway) ) @@ -7743,7 +7803,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { }; const promise = (async () => { const discordOwnership = - record.endpoint.provider === "discord" + leasedChatProvider(record.endpoint.provider) ? await acquireDiscordGatewayOwnership( record.endpoint, context, @@ -7751,7 +7811,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { ) : null; if ( - record.endpoint.provider === "discord" && + leasedChatProvider(record.endpoint.provider) && !discordOwnership && optionsForRuntime.requireDiscordOwnership === true ) { @@ -7796,6 +7856,30 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { enableDiscordGateway: discordOwnership !== null, callbacks: { onMessage: (event) => handleSdkMessage(event, context), + onPhotonAssertOwned: async (activeThreadId) => { + if (!(await runtimeCallbackRecord(endpoint.id, context, ["verifying", "active", "attention"]))) throw new Error("Photon receiver ownership changed"); + if (activeThreadId) { + const working = await db.select({ id: heartbeatRuns.id }).from(chatConversations).innerJoin(heartbeatRuns, and( + eq(heartbeatRuns.companyId, chatConversations.companyId), eq(heartbeatRuns.agentId, record.endpoint.assignedAgentId), + eq(sql`${heartbeatRuns.contextSnapshot}->>'issueId'`, sql`${chatConversations.issueId}::text`), inArray(heartbeatRuns.status, ["queued", "running"]), + )).where(and(eq(chatConversations.companyId, record.endpoint.companyId), eq(chatConversations.endpointId, endpoint.id), eq(chatConversations.externalThreadId, activeThreadId), inArray(chatConversations.state, ["active", "waiting"]))).limit(1); + if (!working.length) throw new Error("Photon conversation has no active agent work"); + } + await db.transaction(async (tx) => { + const current = await runtimeCallbackEndpoint(tx, endpoint.id, context, ["attention"]); + if (!current) return; + const connection = await tx.select().from(toolConnections).where(eq(toolConnections.id, current.connectionId)).then((rows) => rows[0]); + if (!connection?.enabled) throw new Error("Photon endpoint requires operator recovery"); + await tx.update(chatEndpoints).set({ status: current.setup.step === "complete" ? "active" : "verifying", healthMessage: "Photon receiver connected", lastError: null, updatedAt: new Date() }).where(eq(chatEndpoints.id, endpoint.id)); + }); + }, + onPhotonCheckpoint: (sequence) => db.transaction(async (tx) => { + if (!(await runtimeCallbackEndpoint(tx, endpoint.id, context, ["verifying", "active"]))) throw new Error("Photon receiver is no longer current"); + if ((await renewDiscordGatewayOwnershipForMessageAdmission(tx, endpoint.id, context)).kind !== "owned") throw new Error("Photon receiver lease was replaced"); + await writePhotonCheckpoint(new PhotonState({ companyId: record.endpoint.companyId, endpointId: endpoint.id }, createChatSdkStatePersistence(tx as unknown as Db)), credentials.lineId, sequence); + }), + onPhotonEvent: (event) => handlePhotonEvent(endpoint.id, event, context), + onPhotonFailure: (error) => handlePhotonFailure(endpoint.id, error, context), onDiscordRootMentionAdmission: record.endpoint.provider === "discord" ? (event) => @@ -7853,7 +7937,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { const stillRegistered = runtime.get(endpoint.id) === instance; const stillCurrent = latest !== null && - latest.endpoint.status === record.endpoint.status && + (latest.endpoint.status === record.endpoint.status || record.endpoint.provider === "imessage-photon" && record.endpoint.status === "attention" && ["active", "verifying"].includes(latest.endpoint.status)) && runtimeContextForRecord(latest).version === context.version; if (!stillRegistered || !stillCurrent) { if (stillRegistered) { @@ -7918,7 +8002,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { ) .where( and( - eq(chatEndpoints.provider, "discord"), + inArray(chatEndpoints.provider, ["discord", "imessage-photon"]), inArray(chatEndpoints.status, ["verifying", "active", "attention"]), eq(toolConnections.status, "active"), eq(toolConnections.enabled, true), @@ -8050,6 +8134,881 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { }; } + async function inspectPhotonCredentials(projectId: string, projectSecret: string) { + try { + return await new PhotonCloudClient(fetchImpl).inspect(projectId, projectSecret); + } catch (error) { + if (!(error instanceof PhotonError)) throw error; + // Only credentials/allocation failures mean the setup input needs repair. + // Preserve safe provider messages while distinguishing outages and bad + // upstream responses from validation errors. Never return response bodies. + const status = error.code === "credentials" || error.code === "line_unavailable" + ? 422 + : error.code === "quota" + ? 429 + : error.code === "network" + ? 503 + : 502; + throw new HttpError(status, error.message, { + code: `photon_${error.code}`, + }); + } + } + + async function inspectPhoton( + endpointId: string, + input: { projectId: string; projectSecret: string }, + ) { + const record = await endpointRecord(endpointId); + if (!record || record.endpoint.provider !== "imessage-photon") + throw notFound("iMessage Photon endpoint not found"); + const inspection = structuredClone(await inspectPhotonCredentials( + input.projectId, + input.projectSecret, + )); + const reserved = await db + .select({ number: chatEndpoints.botExternalId }) + .from(chatEndpoints) + .where( + and( + eq(chatEndpoints.provider, "imessage-photon"), + ne(chatEndpoints.id, endpointId), + ne(chatEndpoints.status, "archived"), + ), + ); + for (const line of inspection.lines) { + if (reserved.some((row) => row.number === line.phoneNumber)) { + line.eligible = false; + line.unavailableReason = + "This number already belongs to another channel"; + } + } + inspection.eligible = inspection.allocation === "shared" ? inspection.eligible && !reserved.some((row) => row.number === photonSharedIdentity(inspection.projectId)) : inspection.lines.some((line) => line.eligible); + return inspection; + } + + async function handlePhotonFailure( + endpointId: string, + error: unknown, + context: RuntimeContext, + ) { + if (error instanceof PhotonError && error.code === "attachment_not_ready") + return; + error = photonFailure(error); + const fatal = + error instanceof PhotonError && + [ + "credentials", + "line_unavailable", + "history_gap", + "invalid_response", + ].includes(error.code); + const message = + error instanceof PhotonError + ? error.message + : "Photon connection interrupted; reconnecting"; + await db.transaction(async (tx) => { + const endpoint = await runtimeCallbackEndpoint(tx, endpointId, context, [ + "verifying", + "active", + "attention", + ]); + if (!endpoint) return; + await tx + .update(chatEndpoints) + .set({ + status: "attention", + healthMessage: message, + lastError: message, + updatedAt: new Date(), + }) + .where(eq(chatEndpoints.id, endpointId)); + if (fatal) + await tx + .update(toolConnections) + .set({ + enabled: false, + healthStatus: "error", + healthMessage: message, + }) + .where(eq(toolConnections.id, endpoint.connectionId)); + }); + // Retirement is queued outside the receiver callback to avoid self-joining. + void invalidateRuntime(endpointId).catch(() => undefined); + } + + async function processPhotonResponse( + endpoint: EndpointRow, + event: PhotonEvent, + threadId: string, + adapter: PhotonChatAdapter, + context: RuntimeContext, + ): Promise { + const command = + event.type === "message.received" + ? photonResponseCommand(event.message.content.text ?? "") + : null; + const replyGuid = + event.type === "message.received" ? event.message.replyTargetGuid : null; + const prompt = + event.type === "poll.changed" + ? await adapter.state.read( + `poll-message:${event.pollMessageGuid}`, + ) + : replyGuid + ? ((await adapter.state.read( + `prompt-message:${replyGuid}`, + )) ?? + (await adapter.state.read( + `poll-message:${replyGuid}`, + ))) + : null; + const reference = command?.reference ?? prompt?.reference; + if (!reference) { + if ( + !event.isFromMe && + (replyGuid || + (event.type === "poll.changed" && + (event.delta.type === "voted" || event.delta.type === "unvoted"))) + ) { + // A vote can beat the local poll-binding write. Keep it behind the + // checkpoint while a publication in this chat is in flight. + const pending = await db + .select({ id: chatPublications.id }) + .from(chatPublications) + .innerJoin( + chatConversations, + eq(chatConversations.id, chatPublications.conversationId), + ) + .where( + and( + eq(chatPublications.endpointId, endpoint.id), + eq(chatConversations.externalThreadId, threadId), + inArray(chatPublications.state, [ + "pending", + "retry", + "streaming", + "delivery_unknown", + ]), + isNotNull(sql`${chatPublications.payload}->>'interactionId'`), + ), + ) + .limit(1); + if (pending.length) + throw new PhotonError( + "attachment_not_ready", + "Waiting for Photon poll binding", + ); + } + return ( + event.type === "poll.changed" || + (event.type === "message.received" && + /^\/(answer|submit)\b/i.test(event.message.content.text ?? "")) + ); + } + const action = await db + .select() + .from(chatActions) + .where( + and( + eq(chatActions.companyId, endpoint.companyId), + eq(chatActions.endpointId, endpoint.id), + eq(chatActions.kind, "photon_interaction"), + eq(chatActions.providerActionId, `photon:${reference}`), + ), + ) + .then((rows) => rows[0]); + if (!action || action.status !== "issued" || !action.conversationId) + return true; + const binding = action.payload as unknown as PhotonInteractionBinding; + if ( + binding.version !== 1 || + binding.reference !== reference || + !Number.isFinite(Date.parse(binding.expiresAt)) || + Date.parse(binding.expiresAt) <= Date.now() + ) + return true; + const conversation = await db + .select() + .from(chatConversations) + .where( + and( + eq(chatConversations.companyId, endpoint.companyId), + eq(chatConversations.endpointId, endpoint.id), + eq(chatConversations.id, action.conversationId), + ), + ) + .then((rows) => rows[0]); + if ( + !conversation || + !["active", "waiting"].includes(conversation.state) || + conversation.sessionGeneration !== binding.sessionGeneration || + conversation.externalThreadId !== threadId + ) + return true; + const publication = await db + .select() + .from(chatPublications) + .where( + and( + eq(chatPublications.companyId, endpoint.companyId), + eq(chatPublications.endpointId, endpoint.id), + eq(chatPublications.id, binding.publicationId), + eq(chatPublications.conversationId, conversation.id), + ), + ) + .then((rows) => rows[0]); + if ( + !publication || + publication.payload.interactionId !== binding.interactionId || + new Date(event.occurredAt).getTime() < action.createdAt.getTime() + ) + return true; + if ( + ["pending", "retry", "streaming", "delivery_unknown"].includes( + publication.state, + ) + ) + throw new PhotonError( + "attachment_not_ready", + "Waiting for Photon prompt publication", + ); + if (publication.state !== "published") return true; + const interaction = ( + await issueThreadInteractionService(db).listForIssue(conversation.issueId) + ).find((candidate) => candidate.id === binding.interactionId); + if ( + !interaction || + interaction.status !== "pending" || + interaction.companyId !== endpoint.companyId || + interaction.createdByAgentId !== endpoint.assignedAgentId || + !nativePhotonInteraction(interaction) + ) + return true; + const actor = + event.type === "message.received" ? event.message.sender : event.actor; + if (event.isFromMe || !actor?.address || actor.service !== "iMessage") + return true; + const chat = await adapter.chatInfo(threadId); + if ( + chat.isArchived || + !chat.participants.some( + (person) => + person.address === actor.address && person.service === actor.service, + ) + ) + return true; + const principal = await ensurePrincipal( + endpoint, + { + userId: `${actor.service}:${actor.address}`, + userName: actor.address, + fullName: actor.address, + isMe: false, + isBot: false, + }, + event, + ); + if ( + !principal.userId || + principal.linkedDenied || + principal.principal.kind !== "user" || + principal.principal.isBot + ) + return true; + const assertCurrent = async () => + db.transaction((tx) => + requireCurrentExternalActionAuthorization(tx, { + conversationId: conversation.id, + endpointId: endpoint.id, + expectedUserId: principal.userId!, + principalId: principal.principal.id, + runtimeContext: context, + }), + ); + try { + await assertCurrent(); + } catch (error) { + if (isExternalActionAuthorizationChange(error)) return true; + throw error; + } + const draftKey = `draft:${reference}:${principal.principal.id}:${principal.userId}`; + let draft = (await adapter.state.read(draftKey)) ?? { + schema: 1, + interactionId: interaction.id, + principalId: principal.principal.id, + userId: principal.userId, + answers: [], + lastSequence: 0, + }; + if (draft.lastSequence > event.sequence) return true; + const questionIndex = command?.questionIndex ?? prompt?.questionIndex ?? 0; + let answerValue = + command?.value ?? + (event.type === "message.received" + ? (event.message.content.text ?? "") + : ""); + let pollChoice: string | undefined; + if (event.type === "poll.changed") { + if (!prompt || event.pollMessageGuid !== prompt.pollMessageGuid) + return true; + if (event.delta.type !== "voted" && event.delta.type !== "unvoted") + return true; + pollChoice = prompt.options[event.delta.optionIdentifier]; + if (!pollChoice) return true; + if (event.delta.type === "unvoted") { + const questionId = + interaction.kind === "ask_user_questions" + ? interaction.payload.questions[questionIndex]?.id + : null; + await adapter.state.update(draftKey, () => ({ + ...draft, + answers: draft.answers.filter( + (answer) => + answer.questionId !== questionId || + !answer.optionIds.includes(pollChoice!), + ), + decision: draft.decision === pollChoice ? undefined : draft.decision, + lastSequence: event.sequence, + })); + return true; + } + } + const enqueueResponse = async (key: string, text: string) => { + await assertCurrent(); + await db.transaction(async (tx) => { + await tx + .insert(chatActions) + .values({ + companyId: endpoint.companyId, + endpointId: endpoint.id, + conversationId: conversation.id, + kind: "photon_response_notice", + providerActionId: `photon-notice:${key}`, + payload: { + reference, + questionIndex, + publicationId: binding.publicationId, + }, + status: "processed", + }) + .onConflictDoNothing(); + await tx + .insert(chatPublications) + .values({ + companyId: endpoint.companyId, + endpointId: endpoint.id, + conversationId: conversation.id, + issueId: conversation.issueId, + idempotencyKey: key, + payload: { + ...publication.payload, + card: undefined, + text: projectSafeChatPublicationText(text), + }, + state: "pending", + }) + .onConflictDoNothing(); + }); + scheduleMessageProcessing(() => + processPendingPublications().then(() => undefined), + ); + }; + const notice = (text: string) => + enqueueResponse(`photon-response:${reference}:${event.sequence}`, text); + try { + if (interaction.kind === "ask_user_questions") { + if (command?.command !== "submit") { + let answer: AskUserQuestionsAnswer; + if (pollChoice) { + const question = interaction.payload.questions[questionIndex]; + if ( + !question || + !question.options.some((option) => option.id === pollChoice) + ) + return true; + answer = { questionId: question.id, optionIds: [pollChoice] }; + } else + answer = parsePhotonQuestionAnswer( + interaction, + questionIndex, + answerValue, + ); + draft = { + ...draft, + answers: [ + ...draft.answers.filter( + (entry) => entry.questionId !== answer.questionId, + ), + answer, + ], + lastSequence: event.sequence, + }; + } + const answers = interaction.payload.questions.flatMap((question) => + draft.answers.filter((answer) => answer.questionId === question.id), + ); + if ( + interaction.payload.questions.length > 1 && + command?.command !== "submit" + ) { + await adapter.state.update(draftKey, () => draft); + const next = interaction.payload.questions.findIndex( + (question) => + !answers.some((answer) => answer.questionId === question.id), + ); + if (next >= 0) + await enqueueResponse( + `photon-question:${reference}:${next}`, + "Input needed", + ); + else + await notice( + `Your answers are saved. Send /submit ${reference} to submit them.`, + ); + return true; + } + validateNativeQuestionResponseInput(interaction, { answers }); + // Canonical service rechecks required fields, options, audience, and + // pending -> answered atomically with the response-delivery receipt. + const issue = await db + .select() + .from(issues) + .where( + and( + eq(issues.companyId, endpoint.companyId), + eq(issues.id, conversation.issueId), + ), + ) + .then((rows) => rows[0]); + if (!issue) return true; + const answered = await issueThreadInteractionService( + db, + ).answerQuestions( + issue, + interaction.id, + { answers }, + { userId: principal.userId }, + { + beforeResolveInTransaction: async (tx) => { + await requireCurrentExternalActionAuthorization(tx, { + conversationId: conversation.id, + endpointId: endpoint.id, + expectedUserId: principal.userId!, + principalId: principal.principal.id, + runtimeContext: context, + }); + const current = await tx + .select() + .from(chatConversations) + .where(eq(chatConversations.id, conversation.id)) + .then((rows) => rows[0]); + if ( + current?.sessionGeneration !== binding.sessionGeneration || + Date.parse(binding.expiresAt) <= Date.now() + ) + throw forbidden( + "Photon prompt expired or its task generation changed", + ); + }, + afterResolveInTransaction: async (tx, resolved) => { + const claimed = await tx + .update(chatActions) + .set({ + principalId: principal.principal.id, + status: "processed", + result: { + code: "photon_question_answered", + interactionId: resolved.id, + interactionStatus: resolved.status, + answersSha256: nativeSha256( + (resolved as AskUserQuestionsInteraction).result?.answers, + ), + }, + updatedAt: new Date(), + }) + .where( + and( + eq(chatActions.id, action.id), + eq(chatActions.status, "issued"), + ), + ) + .returning({ id: chatActions.id }); + if (!claimed.length) + throw forbidden("Photon prompt has already been resolved"); + await logActivity(tx as unknown as Db, { + companyId: endpoint.companyId, + actorType: "user", + actorId: principal.userId!, + action: "issue.thread_interaction_answered", + entityType: "issue", + entityId: issue.id, + details: { + source: "external_chat", + provider: endpoint.provider, + endpointId: endpoint.id, + conversationId: conversation.id, + interactionId: resolved.id, + }, + }); + }, + }, + ); + scheduleMessageProcessing(async () => { + await questionResponses.deliver(answered.id); + await processPendingPublications(); + }); + } else { + if (command?.command === "submit") return true; + const matched = /^(accept|reject|1|2)(?:\s+([\s\S]+))?$/i.exec( + answerValue.trim(), + ); + const decision = + pollChoice === "accept" || pollChoice === "reject" + ? pollChoice + : matched + ? /^(accept|1)$/i.test(matched[1]) + ? "accept" + : "reject" + : draft.decision; + if (!decision) + throw new PhotonAnswerValidationError( + `Send /answer ${reference} Accept or /answer ${reference} Reject .`, + ); + const reason = + matched?.[2]?.trim() ?? + (draft.decision === "reject" && !pollChoice + ? answerValue.trim() + : ""); + if ( + decision === "reject" && + interaction.payload.rejectRequiresReason && + !reason + ) { + await adapter.state.update(draftKey, () => ({ + ...draft, + decision, + lastSequence: event.sequence, + })); + await notice( + `Give a rejection reason with /answer ${reference} Reject .`, + ); + return true; + } + const issue = await db + .select() + .from(issues) + .where( + and( + eq(issues.companyId, endpoint.companyId), + eq(issues.id, conversation.issueId), + ), + ) + .then((rows) => rows[0]); + if (!issue) return true; + const mutation = { + beforeResolveInTransaction: async (tx: DbTransaction) => { + await requireCurrentExternalActionAuthorization(tx, { + conversationId: conversation.id, + endpointId: endpoint.id, + expectedUserId: principal.userId!, + principalId: principal.principal.id, + runtimeContext: context, + }); + const current = await tx + .select() + .from(chatConversations) + .where(eq(chatConversations.id, conversation.id)) + .then((rows) => rows[0]); + if ( + current?.sessionGeneration !== binding.sessionGeneration || + Date.parse(binding.expiresAt) <= Date.now() + ) + throw forbidden( + "Photon prompt expired or its task generation changed", + ); + }, + afterResolveInTransaction: async ( + tx: DbTransaction, + resolved: IssueThreadInteraction, + ) => { + const claimed = await tx + .update(chatActions) + .set({ + principalId: principal.principal.id, + status: "processed", + result: { + code: "photon_confirmation_resolved", + interactionId: resolved.id, + interactionStatus: resolved.status, + }, + updatedAt: new Date(), + }) + .where( + and( + eq(chatActions.id, action.id), + eq(chatActions.status, "issued"), + ), + ) + .returning({ id: chatActions.id }); + if (!claimed.length) + throw forbidden("Photon prompt has already been resolved"); + await logActivity(tx as unknown as Db, { + companyId: endpoint.companyId, + actorType: "user", + actorId: principal.userId!, + action: + decision === "accept" + ? "issue.thread_interaction_accepted" + : "issue.thread_interaction_rejected", + entityType: "issue", + entityId: issue.id, + details: { + source: "external_chat", + provider: endpoint.provider, + endpointId: endpoint.id, + conversationId: conversation.id, + interactionId: resolved.id, + }, + }); + }, + }; + if (decision === "accept") + await issueThreadInteractionService(db).acceptInteraction( + issue, + interaction.id, + {}, + { userId: principal.userId }, + mutation, + ); + else + await issueThreadInteractionService(db).rejectInteraction( + issue, + interaction.id, + { reason }, + { userId: principal.userId }, + mutation, + ); + scheduleMessageProcessing(async () => { + await processPendingPublications(); + }); + } + } catch (error) { + if (error instanceof PhotonError) throw error; + if (isExternalActionAuthorizationChange(error)) return true; + if (error instanceof HttpError && [403, 404, 409].includes(error.status)) + return true; + // Only validator errors become correction copy. Infrastructure errors + // leave the checkpoint unchanged and retry through durable recovery. + if ( + (error instanceof HttpError && error.status === 422) || + error instanceof PhotonAnswerValidationError + ) { + const missingIndex = interaction.kind === "ask_user_questions" && command?.command === "submit" + ? interaction.payload.questions.findIndex((question) => + question.required !== false && !draft.answers.some((answer) => answer.questionId === question.id)) + : -1; + await notice( + `${redactError(error)} Send /answer ${reference}.${(missingIndex >= 0 ? missingIndex : questionIndex) + 1} to correct it.`, + ); + return true; + } + throw error; + } + return true; + } + + async function handlePhotonEvent( + endpointId: string, + event: PhotonEvent, + context: RuntimeContext, + ) { + const record = await runtimeCallbackRecord(endpointId, context, [ + "verifying", + "active", + "attention", + ]); + if (!record) + throw new Error("Photon receiver no longer owns this endpoint"); + const adapter = context.endpointRuntime!.getProviderAdapter(); + if (!(adapter instanceof PhotonChatAdapter)) + throw new Error("Photon adapter unavailable"); + if (adapter.authentication.identity.allocation === "shared" && event.type === "group.changed") return; + if ( + event.type === "group.changed" && + (event.change.type === "participantRemoved" || + event.change.type === "participantLeft") && + event.change.participant.address === record.endpoint.botExternalId + ) { + const removedThreadId = photonThreadId({ + lineId: adapter.authentication.identity.lineId, + chatGuid: event.chatGuid, + isGroup: true, + }); + await db.transaction(async (tx) => { + if ( + !(await runtimeCallbackEndpoint(tx, endpointId, context, [ + "verifying", + "active", + ])) + ) + throw new Error("Photon receiver is no longer current"); + if ( + ( + await renewDiscordGatewayOwnershipForMessageAdmission( + tx, + endpointId, + context, + ) + ).kind !== "owned" + ) + throw new Error("Photon receiver lease was replaced"); + // Removal can be the first retained event after activation. Preserve a + // tombstone even when the group has never been discovered locally. + await tx + .insert(chatEndpointResources) + .values({ + companyId: record.endpoint.companyId, + endpointId, + providerResourceId: removedThreadId, + type: "group_chat", + label: "Unavailable iMessage group", + enabled: false, + availability: "unavailable", + metadata: { photonRemoved: true }, + }) + .onConflictDoUpdate({ + target: [ + chatEndpointResources.endpointId, + chatEndpointResources.type, + chatEndpointResources.providerResourceId, + ], + set: { + availability: "unavailable", + metadata: sql`jsonb_set(${chatEndpointResources.metadata}, '{photonRemoved}', 'true'::jsonb)`, + updatedAt: new Date(), + }, + }); + }); + await adapter.endTyping(removedThreadId); + return; + } + const groupIdentity = photonThreadId({ lineId: adapter.authentication.identity.lineId, chatGuid: event.chatGuid, isGroup: true }); + const removedGroup = await db.select({ metadata: chatEndpointResources.metadata }).from(chatEndpointResources).where(and(eq(chatEndpointResources.companyId, record.endpoint.companyId), eq(chatEndpointResources.endpointId, endpointId), eq(chatEndpointResources.providerResourceId, groupIdentity))).limit(1).then((rows) => rows[0]); + const readded = event.type === "group.changed" && event.change.type === "participantAdded" && event.change.participant.address === record.endpoint.botExternalId; + // Late events from a removed chat must still advance recovery without a + // now-forbidden chat lookup holding every other conversation behind them. + if (removedGroup?.metadata.photonRemoved === true && !readded) return; + const chat = await adapter.client.chats + .get(event.chatGuid) + .catch((error) => { + throw photonFailure(error); + }); + if (chat.guid !== event.chatGuid || chat.service !== "iMessage" || (chat.isGroup && adapter.authentication.identity.allocation === "shared")) return; + const threadId = photonThreadId({ + lineId: adapter.authentication.identity.lineId, + chatGuid: chat.guid, + isGroup: chat.isGroup, + }); + const removed = + event.type === "group.changed" && + (event.change.type === "participantRemoved" || + event.change.type === "participantLeft") && + event.change.participant.address === record.endpoint.botExternalId; + const available = await db.transaction(async (tx) => { + if ( + !(await runtimeCallbackEndpoint(tx, endpointId, context, [ + "verifying", + "active", + ])) + ) + throw new Error("Photon receiver is no longer current"); + const ownership = await renewDiscordGatewayOwnershipForMessageAdmission( + tx, + endpointId, + context, + ); + if (ownership.kind !== "owned") + throw new Error("Photon receiver lease was replaced"); + const existing = await tx + .select() + .from(chatEndpointResources) + .where( + and( + eq(chatEndpointResources.endpointId, endpointId), + eq(chatEndpointResources.providerResourceId, threadId), + eq( + chatEndpointResources.type, + chat.isGroup ? "group_chat" : "direct_message", + ), + ), + ) + .then((rows) => rows[0]); + const added = + event.type === "group.changed" && + event.change.type === "participantAdded" && + event.change.participant.address === record.endpoint.botExternalId; + const stillRemoved = + removed || (existing?.metadata.photonRemoved === true && !added); + const availability = + stillRemoved || chat.isArchived ? "unavailable" : "available"; + const metadata = { + photonRemoved: stillRemoved, + participants: chat.participants + .slice(0, 256) + .map((p) => ({ address: p.address, service: p.service })), + }; + const label = ( + chat.displayName || chat.participants.map((p) => p.address).join(", ") + ).slice(0, 512); + await tx + .insert(chatEndpointResources) + .values({ + companyId: record.endpoint.companyId, + endpointId, + providerResourceId: threadId, + type: chat.isGroup ? "group_chat" : "direct_message", + label, + enabled: !chat.isGroup, + availability, + metadata, + }) + .onConflictDoUpdate({ + target: [ + chatEndpointResources.endpointId, + chatEndpointResources.type, + chatEndpointResources.providerResourceId, + ], + set: { label, availability, metadata, updatedAt: new Date() }, + }); + return availability === "available"; + }); + if (!available) return; + if ( + !event.isFromMe && + (await processPhotonResponse( + record.endpoint, + event, + threadId, + adapter, + context, + )) + ) + return; + if (event.type === "message.received" && !event.isFromMe) { + await handleSdkMessage( + { + provider: "imessage-photon", + endpointId, + thread: context.endpointRuntime!.thread(threadId), + message: adapter.normalize(event.message, chat), + trigger: chat.isGroup ? "unaddressed_message" : "direct_message", + }, + context, + ); + } + } + async function configure( endpointId: string, input: ConfigureChatEndpointInput, @@ -8058,6 +9017,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { const record = await endpointRecord(endpointId); if (!record) throw notFound("Chat endpoint not found"); if (record.endpoint.provider === "agentmail") throw badRequest("Use the email inbox API for AgentMail"); + if (input.photon && record.endpoint.provider !== "imessage-photon") throw badRequest("Photon configuration is only valid for iMessage Photon"); const suppliedCredentialKeys = Object.keys(input.credentials ?? {}); if (suppliedCredentialKeys.length > 0) { const credentialAction = @@ -8127,7 +9087,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { status: "paused", setup: { ...current.setup, - runtimeGeneration: runtimeGeneration(current.setup) + 1, + runtimeGeneration: runtimeGeneration(current.setup) + (endpoint.provider === "imessage-photon" ? 0 : 1), } as InternalSetupState, updatedAt: pausedAt, }) @@ -8147,6 +9107,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { }) .where( and( + endpoint.provider === "imessage-photon" ? sql`false` : undefined, eq(chatDeliveries.endpointId, endpoint.id), inArray(chatDeliveries.state, ["received", "retry"]), ), @@ -8248,6 +9209,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { }) .where( and( + endpoint.provider === "imessage-photon" ? sql`false` : undefined, eq(chatDeliveries.endpointId, endpoint.id), inArray(chatDeliveries.state, ["received", "retry"]), ), @@ -8263,7 +9225,8 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { lastError: null, setup: { ...current.setup, - runtimeGeneration: runtimeGeneration(current.setup) + 1, + ...(endpoint.provider === "imessage-photon" ? { photonIntakeAfter: resumedAt.toISOString() } : {}), + runtimeGeneration: runtimeGeneration(current.setup) + (endpoint.provider === "imessage-photon" ? 0 : 1), } as InternalSetupState, updatedAt: resumedAt, }) @@ -8287,8 +9250,8 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { if (activatedEndpoint.provider === "discord") await reconcileDiscordCommands(endpoint.id, credentialLease, true); await runtimeFor(activatedEndpoint, { - requireDiscordOwnership: activatedEndpoint.provider === "discord", - waitForDiscordOwnership: activatedEndpoint.provider === "discord", + requireDiscordOwnership: leasedChatProvider(activatedEndpoint.provider), + waitForDiscordOwnership: leasedChatProvider(activatedEndpoint.provider), }); } catch (error) { await invalidateRuntime(endpoint.id).catch(() => undefined); @@ -8464,7 +9427,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { ) { throw unprocessable("Unsupported chat endpoint setup action"); } - if (!webhookPublicBaseUrl && endpoint.provider !== "discord") { + if (!webhookPublicBaseUrl && endpoint.provider !== "discord" && endpoint.provider !== "imessage-photon") { throw unprocessable( `A public HTTPS Paperclip URL is required before connecting ${PROVIDER_LABELS[endpoint.provider]}`, ); @@ -8576,6 +9539,12 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { `${PROVIDER_LABELS[endpoint.provider]} credentials are required`, ); }); + if (endpoint.provider === "imessage-photon") { + const configuration = photonChannelConfigurationSchema.parse(input.photon ?? (credentials.allocation === "shared" ? { allocation: "shared", projectId: credentials.projectId } : { projectId: credentials.projectId, lineId: credentials.lineId })); + const lineId = configuration.allocation === "shared" ? photonSharedScope(configuration.projectId) : configuration.lineId; + if (endpoint.botExternalId && (configuration.projectId !== endpoint.providerAccountId || credentials.lineId && lineId !== credentials.lineId)) throw conflict("A different Photon identity requires a new channel"); + credentials = { ...credentials, ...configuration, lineId }; + } const identity = await verifyCredentials(endpoint.provider, credentials); // Once setup has claimed a provider bot identity, every credential repair // must prove that same identity before secrets can be replaced. A process @@ -8652,6 +9621,12 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { throw error; } } + if (endpoint.provider === "imessage-photon") { + await invalidateRuntime(endpoint.id); + await credentialLease.assertOwned(); + if (credentials.allocation === "shared") await db.update(chatEndpoints).set({ allowGroupChats: false }).where(eq(chatEndpoints.id, endpoint.id)); + await db.update(toolConnections).set({ config: { provider: endpoint.provider, photon: credentials.allocation === "shared" ? { allocation: "shared", projectId: credentials.projectId } : { allocation: "dedicated", projectId: credentials.projectId, lineId: credentials.lineId } } }).where(and(eq(toolConnections.companyId, endpoint.companyId), eq(toolConnections.id, endpoint.connectionId))); + } if ( (input.credentials && Object.keys(input.credentials).length > 0) || credentialsChangedByDiscovery @@ -8702,6 +9677,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { setup: { ...current.setup, step: waitingForSlackConfiguration ? "provider_setup" : "test", + ...(endpoint.provider === "imessage-photon" ? { photonIntakeAfter: (current.setup as InternalSetupState).photonIntakeAfter ?? updatedAt.toISOString() } : {}), testStartedAt: waitingForSlackConfiguration ? null : updatedAt.toISOString(), @@ -8940,7 +9916,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { ); } const requiredTrigger = - endpoint.provider === "telegram" + ["telegram", "imessage-photon"].includes(endpoint.provider) ? "direct_message" : "subscribed_message"; const qualifyingDelivery = await db @@ -8967,7 +9943,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { !qualifyingDelivery.processedAt ) { throw conflict( - endpoint.provider === "telegram" + ["telegram", "imessage-photon"].includes(endpoint.provider) ? "Send the test direct message before completing setup" : "Reply once without mentioning the agent before completing setup", { code: "chat_test_follow_up_missing" }, @@ -9311,9 +10287,13 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { tx, input.endpointId, input.runtimeContext, - ["active"], + ["active", "verifying"], ); - if (!endpoint) { + // Photon setup already admits linked senders and publishes agent prompts. + // Let those prompts resolve so a clarifying question cannot deadlock the + // actual-reply qualification. Other providers retain their active-only gate. + if (!endpoint || (endpoint.status !== "active" && + (endpoint.provider !== "imessage-photon" || endpoint.setup.step !== "test"))) { throw forbidden("This chat action is no longer authorized", { code: "chat_action_authorization_changed", }); @@ -9459,6 +10439,8 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { omissionReasons[reason] = (omissionReasons[reason] ?? 0) + count; }; const boundedAttachments = input.attachments.slice(0, 20); + const photonDerivatives = new Map(); + const originalPhotonAttachments = new Map(); if ( input.endpoint.provider === "microsoft-teams" && input.unavailableReferenceCount @@ -9469,7 +10451,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { omit("download_unavailable", input.unavailableReferenceCount); } if ( - input.endpoint.provider === "github" && + ["github", "imessage-photon"].includes(input.endpoint.provider) && input.attachmentLimitOmissions ) { omit("attachment_limit", input.attachmentLimitOmissions); @@ -9748,7 +10730,10 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { const telegramMedia = input.endpoint.provider === "telegram" && hasTelegramMediaProvenance(attachment); - const sourceBoundMedia = teamsInlineImage || telegramMedia; + const sourceBoundMedia = + teamsInlineImage || + telegramMedia || + input.endpoint.provider === "imessage-photon"; const requireCurrentAttachmentAuthorization = input.endpoint.provider === "github" || sourceBoundMedia; try { @@ -9842,8 +10827,40 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { continue; } } + const photonSourceHash = createHash("sha256") + .update(body) + .digest("hex"); + if (input.endpoint.provider === "imessage-photon") { + await validatePhotonImage(body, contentType); + const companion = takePhotonCompanion(body); + if (companion?.unavailable) omit("companion_unavailable"); + else if (companion) { + const video: Attachment = { type: "video", name: companion.fileName, mimeType: companion.mimeType, size: companion.data.length, fetchData: async () => companion.data }; + photonDerivatives.set(video, { source: attachment, sourceHash: photonSourceHash, kind: "live_photo_video" }); + boundedAttachments.push(video); + } + if ( + HEIF_CONTENT_TYPES.has(contentType) && + isAllowedContentType("image/jpeg") + ) { + try { + const preview = await photonHeifPreview(body); + const derivative: Attachment = { + type: "image", + name: `${originalFilename} (JPEG preview).jpg`, + mimeType: "image/jpeg", + size: preview.length, + fetchData: async () => preview, + }; + photonDerivatives.set(derivative, { source: attachment, sourceHash: photonSourceHash, kind: "heif_jpeg_preview" }); + boundedAttachments.push(derivative); + } catch { + omit("preview_unavailable"); + } + } + } const fingerprint = JSON.stringify([ - createHash("sha256").update(body).digest("hex"), + photonSourceHash, body.length, contentType, originalFilename, @@ -9859,8 +10876,39 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { telegramMedia ? attachment : undefined, ); } + const registerPhotonProvenance = async (attachmentId: string) => { + if (input.endpoint.provider !== "imessage-photon") return; + const relation = photonDerivatives.get(attachment); + if (!relation) { + originalPhotonAttachments.set(attachment, attachmentId); + return; + } + const originalAttachmentId = + originalPhotonAttachments.get(relation.source); + if (!originalAttachmentId) + throw new Error("Photon related attachment source is missing"); + await db + .insert(chatActions) + .values({ + companyId: input.endpoint.companyId, + endpointId: input.endpoint.id, + deliveryId: input.deliveryId, + kind: relation.kind === "heif_jpeg_preview" ? "attachment_derivative" : "attachment_companion", + providerActionId: `photon-${relation.kind}:${input.deliveryId}:${originalAttachmentId}`, + payload: { + originalAttachmentId, + derivativeAttachmentId: attachmentId, + originalSha256: relation.sourceHash, + derivativeSha256: photonSourceHash, + kind: relation.kind, + }, + status: "processed", + }) + .onConflictDoNothing(); + }; if (existingId) { storedIds.push(existingId); + await registerPhotonProvenance(existingId); continue; } const stored = await options.storage.putFile({ @@ -9906,8 +10954,16 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { throw error; } storedIds.push(row.id); + await registerPhotonProvenance(row.id); } catch (error) { if (isExternalActionAuthorizationChange(error)) throw error; + if ( + error instanceof PhotonError && + ["attachment_not_ready", "network", "quota", "credentials"].includes( + error.code, + ) + ) + throw error; // Use the closed current-input omission vocabulary consumed by native // prompts; provider-specific diagnostics remain redacted log codes. omit( @@ -10813,9 +11869,11 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { ) .limit(1); const checkpoint = run.runnerProfileJson?.sessionCheckpoint as - Record | undefined; + | Record + | undefined; const binding = checkpoint?.identity as - Record | undefined; + | Record + | undefined; if ( !event && !result && @@ -11057,7 +12115,8 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { return; } const checkpoint = run.runnerProfileJson?.sessionCheckpoint as - Record | undefined; + | Record + | undefined; if ( !run.nativeSessionId || !run.runnerInstanceId || @@ -11083,7 +12142,8 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { ? checkpoint.providerSessionId : null, recoveryMode: coordinator.failureDetail!.recoveryMode as - "bootstrap_retry" | "exact_checkpoint_resume", + | "bootstrap_retry" + | "exact_checkpoint_resume", allowVerifiedBackup: leases.length > 0, }) ) @@ -11224,11 +12284,14 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { ) .for("share", { noWait: true }); const result = accepted?.resultJson.result as - Record | undefined; + | Record + | undefined; const terminal = accepted?.resultJson.terminal as - Record | undefined; + | Record + | undefined; const continuation = result?.continuation as - Record | undefined; + | Record + | undefined; if ( failedRun.runtimeMode !== "native" || failedRun.nativeIssueId !== issue.id || @@ -11546,8 +12609,8 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { ), ); if (sources.length !== commentIds.length) throw failedChatRetryDenied(); - const ordered = commentIds.map((id) => - sources.find((action) => action.payload.commentId === id)!, + const ordered = commentIds.map( + (id) => sources.find((action) => action.payload.commentId === id)!, ); const first = ordered[0]!; if ( @@ -11659,7 +12722,8 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { issueId: issue.id, commentId: comment.id, requestedByActorType: action.payload.requestedByActorType as - "user" | "system", + | "user" + | "system", requestedByActorId: String(action.payload.requestedByActorId), requestedAt: action.createdAt, authorize: async () => {}, @@ -11759,7 +12823,8 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { principalId: first.principalId, sessionGeneration: destination.conversation.sessionGeneration, requestedByActorType: first.payload.requestedByActorType as - "user" | "system", + | "user" + | "system", requestedByActorId: String(first.payload.requestedByActorId), retryAncestors: [], }; @@ -11983,7 +13048,8 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { await assertFailedNativeRetryState(tx, source, input.companyId); const context = input.contextSnapshot; const hint = context.chatFailedRunRetry as - Record | undefined; + | Record + | undefined; if ( context.issueId !== source.issueId || context.source !== `chat:${source.provider}` || @@ -12921,7 +13987,8 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { issueId: String(action.payload.issueId), commentId: String(action.payload.commentId), requestedByActorType: action.payload.requestedByActorType as - "user" | "system", + | "user" + | "system", requestedByActorId: String(action.payload.requestedByActorId), requestedAt: action.createdAt, authorize: async () => {}, @@ -13106,7 +14173,8 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { issueId: claimed.payload.issueId, commentId: claimed.payload.commentId, requestedByActorType: claimed.payload.requestedByActorType as - "user" | "system", + | "user" + | "system", requestedByActorId: String(claimed.payload.requestedByActorId), requestedAt: claimed.createdAt, authorize: async (tx) => { @@ -13144,7 +14212,8 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { taskKey: context.issue.identifier, wakeCommentId: request.commentId, attachmentOmissionReasons: claimed.payload.attachmentOmissionReasons as - Record | undefined, + | Record + | undefined, durableChatRequest: request, rethrowOnError: true, }); @@ -13205,7 +14274,6 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { } } - async function processMessage( endpoint: EndpointRow, thread: Thread, @@ -13256,6 +14324,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { const providerEventId = `${durableExternalThreadIdentity(thread.id)}:${message.id}`; const surfaceKind = chatSurfaceKind(endpoint.provider, thread); const addressed = + endpoint.provider === "imessage-photon" || trigger === "mention" || trigger === "direct_message" || message.isMention === true; @@ -13324,10 +14393,10 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { ? telegramMessageSentAt(message.raw) : endpoint.provider === "slack" ? slackMessageSentAt(message.raw, message.id) - : message.metadata.dateSent instanceof Date && - Number.isFinite(message.metadata.dateSent.getTime()) - ? message.metadata.dateSent - : null; + : message.metadata.dateSent instanceof Date && + Number.isFinite(message.metadata.dateSent.getTime()) + ? message.metadata.dateSent + : null; const providerSentAtSource = providerSentAt ? endpoint.provider === "microsoft-teams" ? "teams_activity_timestamp" @@ -13335,7 +14404,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { ? "telegram_message_date" : endpoint.provider === "slack" ? "slack_message_ts" - : null + : null : null; const providerUrl = chatProviderConversationUrl({ @@ -13372,13 +14441,15 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { kind: eventKind, trigger, ...(teamsPersonalRecipient ? { teamsPersonalRecipient } : {}), - ...(slackSlashControl ? { admission: { origin: "slack_slash_control" } } : suppressSetupDestinationActivation - ? // A slash-command root is provider-confirmed only after an enabled - // destination authorized its transport. Persist that closed origin so - // crash recovery can never reinterpret it as first-time setup traffic - // and undo a later operator reach revocation. - { admission: { origin: "provider_confirmed_action" } } - : {}), + ...(slackSlashControl + ? { admission: { origin: "slack_slash_control" } } + : suppressSetupDestinationActivation + ? // A slash-command root is provider-confirmed only after an enabled + // destination authorized its transport. Persist that closed origin so + // crash recovery can never reinterpret it as first-time setup traffic + // and undo a later operator reach revocation. + { admission: { origin: "provider_confirmed_action" } } + : {}), ...(runtimeContext ? { runtimeContext: { @@ -13420,6 +14491,9 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { }, message: { providerMessageId: message.id, + ...(endpoint.provider === "imessage-photon" + ? { photonReply: photonReplyReference(message.raw) } + : {}), providerMessageSequence: endpoint.provider === "telegram" ? telegramMessageSequence(message.raw) @@ -13430,10 +14504,14 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { mentionedBot: message.isMention === true, providerSentAt: providerSentAt?.toISOString() ?? null, ...(providerSentAtSource ? { providerSentAtSource } : {}), - ...(endpoint.provider === "github" && - githubAttachmentLimitOmissions(message) + ...((endpoint.provider === "github" || + endpoint.provider === "imessage-photon") && + (githubAttachmentLimitOmissions(message) || + Math.max(0, message.attachments.length - 20)) ? { - attachmentLimitOmissions: githubAttachmentLimitOmissions(message), + attachmentLimitOmissions: + githubAttachmentLimitOmissions(message) || + Math.max(0, message.attachments.length - 20), } : {}), attachments: message.attachments.slice(0, 20).map((attachment) => ({ @@ -13445,7 +14523,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { : nativeInboundAttachments.includes(attachment) ? endpointRuntime.attachmentRecoveryDescriptor( attachment, - endpoint.provider === "telegram" + ["telegram", "imessage-photon"].includes(endpoint.provider) ? attachmentSource : undefined, ) @@ -13539,7 +14617,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { !staleActivation; if ( endpointAccepting && - endpoint.provider === "discord" && + leasedChatProvider(endpoint.provider) && runtimeContext && !admittedDeliveryId ) { @@ -13657,12 +14735,24 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { resource, ); } + if ( + endpointAccepting && + endpoint.provider === "imessage-photon" && + !thread.isDM + ) { + const resource = await ensureResource(endpoint, thread, false, tx); + destinationAccepting = + resource.enabled && + resource.availability === "available" && + currentEndpoint.allowGroupChats; + } const accepting = endpointAccepting && destinationAccepting; const redactDestinationDelivery = (!accepting && thread.isDM) || (!thread.isDM && (endpoint.provider === "microsoft-teams" || - endpoint.provider === "telegram") && + endpoint.provider === "telegram" || + endpoint.provider === "imessage-photon") && (!accepting || provisionalTeamsSetupReply)); const ignoredAt = accepting ? null : new Date(); const inactiveReason = !endpointAccepting @@ -14153,6 +15243,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { message.raw, ); const mayEnableSetupDestination = + endpoint.provider !== "imessage-photon" && !thread.isDM && endpoint.status === "verifying" && addressed && @@ -14263,9 +15354,14 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { if ( isLinear && existingConversation && - (existingConversation.state === "completed" || - existingIssue?.status === "done" || - existingIssue?.status === "cancelled") + (endpoint.provider === "imessage-photon" + // A reply finishes an iMessage turn, not the conversation. Only a + // delivered /new or /close releases this chat's task binding. Use + // the durable control receipt so pre-fix completed rows also resume. + ? await hasCommittedTaskControlCompletion(existingConversation.id) + : existingConversation.state === "completed" || + existingIssue?.status === "done" || + existingIssue?.status === "cancelled") ) { if (existingConversation.state !== "completed") { await db @@ -14394,18 +15490,73 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { } const filterPreControlSource = async (database: DbOrTransaction) => { - if (controlCommand === "status" || (controlCommand === "new" && endpoint.provider === "telegram" && surfaceKind === "native_thread") || guidanceCommand || (await readChatControlChronology(database, endpoint, thread, activeDelivery)) !== "before_or_unproven") return false; + if ( + controlCommand === "status" || + (controlCommand === "new" && + endpoint.provider === "telegram" && + surfaceKind === "native_thread") || + guidanceCommand || + (await readChatControlChronology( + database, + endpoint, + thread, + activeDelivery, + )) !== "before_or_unproven" + ) + return false; const filteredAt = new Date(); - await database.update(chatDeliveries).set({ - state: "filtered", - nextAttemptAt: null, - processedAt: filteredAt, - updatedAt: filteredAt, - redactedError: "Message predates or cannot be ordered after a completed chat close/new. Send a new request to start work.", - }).where(and(eq(chatDeliveries.id, activeDelivery.id), eq(chatDeliveries.companyId, endpoint.companyId), eq(chatDeliveries.state, "processing"))); + await database + .update(chatDeliveries) + .set({ + state: "filtered", + nextAttemptAt: null, + processedAt: filteredAt, + updatedAt: filteredAt, + redactedError: + "Message predates or cannot be ordered after a completed chat close/new. Send a new request to start work.", + }) + .where( + and( + eq(chatDeliveries.id, activeDelivery.id), + eq(chatDeliveries.companyId, endpoint.companyId), + eq(chatDeliveries.state, "processing"), + ), + ); return true; }; if (await filterPreControlSource(db)) return; + const photonQuote = + endpoint.provider === "imessage-photon" + ? photonReplyReference(message.raw) + : null; + if (controlCommand && photonQuote) { + const source = await db + .select({ conversationId: chatMessageLinks.conversationId }) + .from(chatMessageLinks) + .where( + and( + eq(chatMessageLinks.companyId, endpoint.companyId), + eq(chatMessageLinks.endpointId, endpoint.id), + eq(chatMessageLinks.providerMessageId, photonQuote.guid), + ), + ) + .limit(1) + .then((rows) => rows[0]); + if (!source || source.conversationId !== existingConversation?.id) { + await db + .update(chatDeliveries) + .set({ + state: "filtered", + processedAt: new Date(), + nextAttemptAt: null, + redactedError: + "Quoted control does not belong to the current task generation", + updatedAt: new Date(), + }) + .where(eq(chatDeliveries.id, activeDelivery.id)); + return; + } + } const emptySlackMention = endpoint.provider === "slack" && @@ -14663,6 +15814,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { return; } + const inboundActivityPublications: ActivityPublication[] = []; const persistTaskMutation = async ( taskTx: DbOrTransaction, taskEndpoint: EndpointRow, @@ -14826,10 +15978,33 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { authorType: taskUserId ? "user" : "system", metadata: { version: 1, + ...(endpoint.provider === "imessage-photon" + ? { sourceChannel: "imessage-photon" as const } + : {}), sections: [ { title: `${PROVIDER_LABELS[endpoint.provider]} sender`, rows: [ + ...(endpoint.provider === "imessage-photon" && + photonReplyReference(message.raw) + ? [ + { + type: "key_value" as const, + label: "Reply to message", + value: photonReplyReference(message.raw)!.guid, + }, + ...(photonReplyReference(message.raw)!.part + ? [ + { + type: "key_value" as const, + label: "Reply part", + value: photonReplyReference(message.raw)! + .part!, + }, + ] + : []), + ] + : []), { type: "key_value", label: "Name", @@ -14912,6 +16087,26 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { principalId: principalResolution.principal.id, actorUserId: taskUserId, }); + if (taskEndpoint.provider === "imessage-photon") { + await logActivity( + taskTx as Db, + { + companyId: taskEndpoint.companyId, + actorType: taskUserId ? "user" : "system", + actorId: taskUserId ?? "chat:imessage-photon", + action: "issue.comment_added", + entityType: "issue", + entityId: issue.id, + details: { + commentId: comment.id, + issueIdentifier: issue.identifier, + source: "chat:imessage-photon", + endpointId: taskEndpoint.id, + }, + }, + inboundActivityPublications, + ); + } return { actorUserId: taskUserId, comment, conversation, issue }; }; const taskMutation = await db.transaction(async (tx) => { @@ -15041,6 +16236,11 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { ); }); if (!taskMutation) return; + // The open task can fetch the comment immediately, before attachments + // finish preparing or the agent starts. Never publish an uncommitted row. + for (const publication of inboundActivityPublications) { + publishActivity(publication); + } const { actorUserId, comment, conversation, issue } = taskMutation; const attachmentResult = await ingestAttachments({ endpoint, @@ -15639,6 +16839,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { recovery?: unknown; }>; attachmentLimitOmissions?: unknown; + photonReply?: { guid?: unknown; part?: unknown }; }; conversation?: { providerUrl?: unknown }; }; @@ -15677,6 +16878,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { if (rehydrated) return rehydrated; if ( isTeams || + endpointRuntime.provider === "imessage-photon" || (endpointRuntime.provider === "telegram" && typeof attachment.recovery === "object" && attachment.recovery !== null && @@ -15725,7 +16927,14 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { ? normalized.message.text : "", formatted: { type: "root", children: [] }, - raw: {}, + raw: + endpointRuntime.provider === "imessage-photon" && + typeof normalized.message?.photonReply?.guid === "string" + ? { + replyTargetGuid: normalized.message.photonReply.guid, + threadOriginatorPart: normalized.message.photonReply.part, + } + : {}, author: { userId: externalId, userName: @@ -15745,7 +16954,10 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { links: [], isMention: normalized.message?.mentionedBot === true, } as unknown as Message; - if (endpointRuntime.provider === "github") { + if ( + endpointRuntime.provider === "github" || + endpointRuntime.provider === "imessage-photon" + ) { restoreGitHubAttachmentLimitOmissions( message, normalized.message?.attachmentLimitOmissions, @@ -16101,7 +17313,10 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { async function handleSdkMessage( event: ChatSdkMessageCallbackEvent, runtimeContext?: RuntimeContext, - messageOptions: { receiptReactionSupported?: boolean; slackSlashControl?: boolean } = {}, + messageOptions: { + receiptReactionSupported?: boolean; + slackSlashControl?: boolean; + } = {}, ) { if ( event.provider === "discord" && @@ -16125,7 +17340,8 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { event.thread, message, message === event.message ? event.trigger : "subscribed_message", - options.deferWebhookProcessing === true, + event.provider === "imessage-photon" || + options.deferWebhookProcessing === true, null, runtimeContext, event.providerUpdateId, @@ -20004,7 +21220,8 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { ) .then((rows) => (rows.length === 1 ? rows[0]! : null)); const linkedPayload = currentMessageBinding?.publication.payload as - SafeChatPublicationPayload | undefined; + | SafeChatPublicationPayload + | undefined; const linkStillAuthoritative = currentMessageBinding?.link.publicationId === originalPublication.id || (currentMessageBinding?.publication.idempotencyKey === @@ -21721,13 +22938,24 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { invocation.sourceKind === "direct_message" ? "No task is active. Send a message to start a new Paperclip task." : "No active task is bound here. Open its Discord thread to manage it."; - } else if ((await readChatControlChronology( - tx, - record.endpoint, - { id: conversation.externalThreadId, channelId: conversation.externalConversationId }, - { receivedAt: new Date(), normalizedEvent: { message: { providerMessageId: invocation.interactionId } } }, - )) === "before_or_unproven") { - content = "This command predates an already completed chat control. Send a new command for the current conversation."; + } else if ( + (await readChatControlChronology( + tx, + record.endpoint, + { + id: conversation.externalThreadId, + channelId: conversation.externalConversationId, + }, + { + receivedAt: new Date(), + normalizedEvent: { + message: { providerMessageId: invocation.interactionId }, + }, + }, + )) === "before_or_unproven" + ) { + content = + "This command predates an already completed chat control. Send a new command for the current conversation."; } else { const publicText = invocation.command === "new" @@ -26115,7 +27343,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { .where( and( sql`not exists (select 1 from chat_endpoints e where e.id = ${chatDeliveries.endpointId} and e.provider = 'agentmail')`, - onlyDeliveryId ? eq(chatDeliveries.id, onlyDeliveryId) : undefined, + onlyDeliveryId ? eq(chatDeliveries.id, onlyDeliveryId) : undefined, inArray(chatDeliveries.eventKind, [ "reaction_added", "reaction_removed", @@ -26307,7 +27535,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { } async function listResources(endpointId: string) { - return db + const resources = await db .select() .from(chatEndpointResources) .where( @@ -26317,6 +27545,20 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { ), ) .orderBy(asc(chatEndpointResources.label)); + return resources.map((resource) => ({ + ...resource, + participants: + resource.providerResourceId.startsWith("imessage-photon:") && + Array.isArray(resource.metadata.participants) + ? resource.metadata.participants.flatMap((participant) => { + const address = + participant && typeof participant === "object" + ? (participant as Record).address + : null; + return typeof address === "string" ? [address] : []; + }) + : undefined, + })); } async function replaceResources( @@ -26327,6 +27569,8 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { const initial = await endpointRecord(endpointId); if (!initial) throw notFound("Chat endpoint not found"); if (updates.length === 0) return listResources(endpointId); + if (initial.endpoint.provider === "imessage-photon" && initial.endpoint.botExternalId?.startsWith("photon-project:") && updates.some((entry) => entry.enabled)) + throw unprocessable("Photon shared channels support direct messages only; groups cannot be enabled"); await withCredentialMutationLease( initial.endpoint, async (credentialLease) => { @@ -26844,7 +28088,8 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { issueTitle: issue?.title ?? null, isDirectMessage: conversation.isDirectMessage, state: - issue?.status === "done" || issue?.status === "cancelled" + record.endpoint.provider !== "imessage-photon" && + (issue?.status === "done" || issue?.status === "cancelled") ? "completed" : conversation.state, lastActivityAt: conversation.lastActivityAt?.toISOString() ?? null, @@ -27633,6 +28878,27 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { } } + if ( + initialRecord.endpoint.provider === "imessage-photon" && + action === "retry_anyway" + ) { + await tx + .insert(chatActions) + .values({ + companyId: publication.companyId, + endpointId, + conversationId: publication.conversationId, + kind: "photon_publication_retry", + providerActionId: `photon-retry:${publication.id}:${publication.attempts + 1}`, + payload: { + publicationId: publication.id, + attempt: publication.attempts + 1, + }, + result: { authorizedByUserId: userId }, + status: "processed", + }) + .onConflictDoNothing(); + } const now = new Date(); await tx .update(chatPublications) @@ -28655,7 +29921,8 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { commentId: string, ) { const emailBoundary = await endpointRecord(endpointId); - if (emailBoundary?.endpoint.publicationMode === "explicit") throw badRequest("Use an explicit email send action"); + if (emailBoundary?.endpoint.publicationMode === "explicit") + throw badRequest("Use an explicit email send action"); const conversation = await db .select() .from(chatConversations) @@ -28729,7 +29996,8 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { attachmentIds: string[] = [], ) { const emailBoundary = await endpointRecord(endpointId); - if (emailBoundary?.endpoint.publicationMode === "explicit") throw badRequest("Use an explicit email send action"); + if (emailBoundary?.endpoint.publicationMode === "explicit") + throw badRequest("Use an explicit email send action"); // Browser request IDs are only unique within the conversation that issued // them. Include that durable task boundary so a retried key from another // conversation can neither suppress its send nor return the first task's @@ -28937,8 +30205,8 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { const attachedFileById = new Map( attachedFiles.map((file) => [file.id, file]), ); - const orderedFiles = attachmentIds.map((attachmentId) => - attachedFileById.get(attachmentId)!, + const orderedFiles = attachmentIds.map( + (attachmentId) => attachedFileById.get(attachmentId)!, ); const publicationCreatedAt = new Date(); const [created] = await tx @@ -29414,11 +30682,14 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { }) : null; const originalConversation = delivery.normalizedEvent.conversation as - Record | undefined; + | Record + | undefined; const originalMessage = delivery.normalizedEvent.message as - Record | undefined; + | Record + | undefined; const originalPrincipal = delivery.normalizedEvent.principal as - Record | undefined; + | Record + | undefined; if ( !binding || originalConversation?.isDirectMessage !== true || @@ -29523,7 +30794,8 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { issueId: conversation.issueId, commentId: source.comment.id, requestedByActorType: action.payload.requestedByActorType as - "user" | "system", + | "user" + | "system", requestedByActorId: String(action.payload.requestedByActorId), requestedAt: action.createdAt, authorize: async () => {}, @@ -29818,7 +31090,8 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { ) .for("share", { noWait: true }); const marker = run?.resultJson?.nativeCommittedChatResponse as - Record | undefined; + | Record + | undefined; if ( !run || !marker || @@ -30090,7 +31363,8 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { continuing && { ...continuing, phase: continuing.phase as - "consent_unknown" | "file_info_unknown", + | "consent_unknown" + | "file_info_unknown", }, )); if (!current) throw denied(); @@ -31372,6 +32646,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { payload: SafeChatPublicationPayload; replaceProviderMessageId?: string | null; telegramDraftControl?: TelegramDraftControl; + beforePhotonWrite?(): Promise; onSlackFileUploadAccepted?: ( receipt: SlackFileUploadAcceptedReceipt, ) => Promise; @@ -31455,6 +32730,233 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { } } } + if (input.endpoint.provider === "imessage-photon") { + const adapter = endpointRuntime.getProviderAdapter(); + if (!(adapter instanceof PhotonChatAdapter)) + throw new Error("Photon publication runtime unavailable"); + const assertCurrent = async () => { + await input.beforePhotonWrite?.(); + const current = await endpointRecord(input.endpoint.id); + if ( + !current || + !["verifying", "active"].includes(current.endpoint.status) || + runtimeGeneration(current.endpoint.setup) !== + runtimeGeneration(input.endpoint.setup) || + runtime.get(input.endpoint.id) !== endpointRuntime + ) + throw new PhotonError( + "rejected", + "Photon publication authority changed", + ); + const resource = await db + .select() + .from(chatEndpointResources) + .where( + and( + eq(chatEndpointResources.companyId, current.endpoint.companyId), + eq(chatEndpointResources.endpointId, current.endpoint.id), + eq(chatEndpointResources.id, input.conversation.resourceId!), + ), + ) + .then((rows) => rows[0]); + if ( + resource?.availability !== "available" || + (!input.conversation.isDirectMessage && !resource.enabled) || + (input.conversation.isDirectMessage && + !current.endpoint.allowDirectMessages) + ) + throw new PhotonError( + "rejected", + "Photon conversation is no longer enabled", + ); + }; + const retry = await db + .select() + .from(chatActions) + .where( + and( + eq(chatActions.endpointId, input.endpoint.id), + eq(chatActions.kind, "photon_publication_retry"), + eq( + chatActions.providerActionId, + `photon-retry:${input.publication.id}:${input.publication.attempts + 1}`, + ), + eq(chatActions.status, "processed"), + ), + ) + .then((rows) => rows[0]); + const retryUnknown = Boolean(retry); + const nextQuestion = + /^photon-question:([a-zA-Z0-9_-]{8,24}):(\d{1,2})$/.exec( + input.publication.idempotencyKey, + ); + if ( + input.payload.interactionId && + (input.publication.idempotencyKey === + `interaction:${input.payload.interactionId}:${input.endpoint.id}` || + nextQuestion) + ) { + const action = await db + .select() + .from(chatActions) + .where( + and( + eq(chatActions.companyId, input.endpoint.companyId), + eq(chatActions.endpointId, input.endpoint.id), + eq(chatActions.kind, "photon_interaction"), + nextQuestion + ? eq(chatActions.providerActionId, `photon:${nextQuestion[1]}`) + : eq( + sql`${chatActions.payload}->>'publicationId'`, + input.publication.id, + ), + eq(chatActions.status, "issued"), + ), + ) + .then((rows) => rows[0]); + const interaction = ( + await issueThreadInteractionService(db).listForIssue( + input.publication.issueId, + ) + ).find((candidate) => candidate.id === input.payload.interactionId); + if ( + action && + interaction?.status === "pending" && + nativePhotonInteraction(interaction) + ) { + const promptGuard = async () => { + await assertCurrent(); + const current = await db + .select({ status: issueThreadInteractions.status }) + .from(issueThreadInteractions) + .where( + and( + eq( + issueThreadInteractions.companyId, + input.endpoint.companyId, + ), + eq(issueThreadInteractions.id, interaction.id), + ), + ) + .then((rows) => rows[0]); + if (current?.status !== "pending") + throw new PhotonError( + "rejected", + "This interaction has already been resolved", + ); + }; + const receipt = await publishPhotonPrompt({ + adapter, + threadId: thread.id, + binding: action.payload as unknown as PhotonInteractionBinding, + interaction, + questionIndex: nextQuestion ? Number(nextQuestion[2]) : 0, + taskUrl: safeChatTaskUrl( + options.publicBaseUrl, + input.publication.issueId, + ), + assertCurrent: promptGuard, + retryUnknown, + }); + return { id: receipt.promptMessageGuid }; + } + if (nextQuestion) + throw new PhotonError( + "rejected", + "This question is no longer available", + ); + } + const reply = await adapter.state.read<{ guid: string | null }>( + `publication-reply:${input.publication.id}`, + ); + let replyTo = reply?.guid ?? null; + if (!reply) { + const sourceRunId = await receiptReactionCompletionRunId( + db, + input.publication, + input.payload, + ); + if (sourceRunId) { + const source = await db + .select({ guid: chatMessageLinks.providerMessageId }) + .from(heartbeatRuns) + .innerJoin( + chatMessageLinks, + and( + eq(chatMessageLinks.companyId, input.endpoint.companyId), + eq(chatMessageLinks.endpointId, input.endpoint.id), + eq(chatMessageLinks.conversationId, input.conversation.id), + eq(chatMessageLinks.direction, "inbound"), + or( + sql`${chatMessageLinks.commentId}::text = ${heartbeatRuns.contextSnapshot}->>'wakeCommentId'`, + sql`coalesce(${heartbeatRuns.contextSnapshot}->'wakeCommentIds', '[]'::jsonb) ? ${chatMessageLinks.commentId}::text`, + ), + ), + ) + .where( + and( + eq(heartbeatRuns.id, sourceRunId), + eq(heartbeatRuns.companyId, input.endpoint.companyId), + ), + ) + .orderBy(desc(chatMessageLinks.createdAt)) + .limit(1) + .then((rows) => rows[0]); + replyTo = source?.guid ?? null; + } + replyTo = ( + await adapter.state.update<{ guid: string | null }>( + `publication-reply:${input.publication.id}`, + (current) => current ?? { guid: replyTo }, + ) + ).guid; + } + const sent = await adapter.publish( + thread.id, + input.publication.id, + { markdown: text, files }, + { assertCurrent, retryUnknown, replyTo: replyTo ?? undefined }, + ); + if (input.publication.idempotencyKey.startsWith("photon-response:")) { + const notice = await db + .select() + .from(chatActions) + .where( + and( + eq(chatActions.companyId, input.endpoint.companyId), + eq(chatActions.endpointId, input.endpoint.id), + eq(chatActions.kind, "photon_response_notice"), + eq( + chatActions.providerActionId, + `photon-notice:${input.publication.idempotencyKey}`, + ), + ), + ) + .then((rows) => rows[0]); + if ( + notice && + typeof notice.payload.reference === "string" && + typeof notice.payload.questionIndex === "number" && + typeof notice.payload.publicationId === "string" + ) { + const receipt: PhotonPromptReceipt = { + schema: 1, + reference: notice.payload.reference, + questionIndex: notice.payload.questionIndex, + publicationId: notice.payload.publicationId, + promptMessageGuid: sent.id, + promptMessageGuids: sent.messageIds, + options: {}, + }; + for (const guid of sent.messageIds) + await adapter.state.update( + `prompt-message:${guid}`, + (current) => current ?? receipt, + ); + } + } + return sent; + } if ( input.endpoint.provider === "discord" && input.payload.transportPart?.mode === "discord_markdown_attachment" @@ -32062,6 +33564,10 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { publication: typeof chatPublications.$inferSelect, ): Promise { const progress = publication.payload.progressState; + if (progress && ["queued", "working"].includes(progress)) { + const endpoint = await endpointRecord(publication.endpointId); + if (endpoint?.endpoint.provider === "imessage-photon") return "iMessage uses typing instead of progress bubbles"; + } if ( !progress || !["queued", "working", "waiting_for_input"].includes(progress) @@ -35395,7 +36901,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { publication, endpoint, ); - const replaceProviderMessageId = CAPABILITIES[endpoint.provider] + const replaceProviderMessageId = endpoint.provider !== "imessage-photon" && CAPABILITIES[endpoint.provider] .messageEdits ? (closedProgress?.providerMessageId ?? (await interactionResolutionPublicationToReplace( @@ -35498,6 +37004,10 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { payload, replaceProviderMessageId, telegramDraftControl: telegramDraft?.control, + beforePhotonWrite: () => db.transaction(async (tx) => { + await credentialLease.assertOwned(tx); + if (!(await authorizeRetainedChatSourcePublication(tx, publication))) throw new PhotonError("rejected", "Publication source authorization changed"); + }), onSlackFileUploadAccepted: async (receipt) => { // uploadV2 has completed at this point. Mark acceptance // before the durable callback so a local write failure is @@ -36134,6 +37644,7 @@ export function chatChannelService(db: Db, options: ChatChannelServiceOptions) { update, generateSetupSecret, configure, + inspectPhoton, test, handleWebhook, listResources, diff --git a/server/src/services/chat-interaction-publications.ts b/server/src/services/chat-interaction-publications.ts index 1c42ae058a..49ab8c0b3f 100644 --- a/server/src/services/chat-interaction-publications.ts +++ b/server/src/services/chat-interaction-publications.ts @@ -1,3 +1,4 @@ +import { nativePhotonInteraction } from "./photon/interactions.js"; import { randomBytes } from "node:crypto"; import { and, eq, inArray, sql } from "drizzle-orm"; import type { Db } from "@paperclipai/db"; @@ -313,6 +314,7 @@ export async function enqueueIssueInteractionChatPublications( ) : null; const supportsCallbacks = + endpoint.provider !== "imessage-photon" && formDraft === null && question !== null && endpoint.capabilities.actions === true; @@ -430,7 +432,13 @@ export async function enqueueIssueInteractionChatPublications( .onConflictDoNothing() .returning(); const publication = rows[0]; - if (publication && formDraft) { + if (publication && endpoint.provider === "imessage-photon" && nativePhotonInteraction(interaction)) { + const reference = randomBytes(9).toString("base64url"); + await db.insert(chatActions).values({ companyId: interaction.companyId, endpointId: endpoint.id, conversationId: conversation.id, + kind: "photon_interaction", providerActionId: `photon:${reference}`, + payload: { version: 1, reference, interactionId: interaction.id, publicationId: publication.id, sessionGeneration: conversation.sessionGeneration, + expiresAt: new Date(publication.createdAt.getTime() + CHAT_QUESTION_ACTION_TOKEN_TTL_MS).toISOString() }, status: "issued" }); + } else if (publication && formDraft) { await db.insert(chatActions).values( chatQuestionFormActionRecords(formDraft, { companyId: interaction.companyId, @@ -540,10 +548,12 @@ export async function enqueueTerminalIssueInteractionChatPublications( and( eq(chatActions.companyId, interaction.companyId), inArray(chatActions.kind, [ + "photon_interaction", "question_answer", "question_form_open", "question_form_submit", "confirmation_response", + "photon_interaction", ]), eq(chatActions.status, "issued"), eq( @@ -763,6 +773,7 @@ export async function cancelPendingIssueInteractionChatPublications( "question_form_open", "question_form_submit", "confirmation_response", + "photon_interaction", ]), eq(chatActions.status, "issued"), inArray(sql`${chatActions.payload}->>'interactionId'`, [ diff --git a/server/src/services/chat-provider-lifecycle.ts b/server/src/services/chat-provider-lifecycle.ts index 415e1ae654..f7e25abccd 100644 --- a/server/src/services/chat-provider-lifecycle.ts +++ b/server/src/services/chat-provider-lifecycle.ts @@ -493,6 +493,7 @@ export function parseChatProviderLifecycle( input: ParseChatProviderLifecycleInput, ): ChatProviderLifecycleEffect[] { switch (input.provider) { + case "imessage-photon": return []; // Authenticated gRPC events own lifecycle. case "agentmail": return []; case "slack": return parseSlackLifecycle(input); diff --git a/server/src/services/chat-publication-errors.ts b/server/src/services/chat-publication-errors.ts index ac43f7826a..3d020bbc49 100644 --- a/server/src/services/chat-publication-errors.ts +++ b/server/src/services/chat-publication-errors.ts @@ -192,6 +192,23 @@ export function classifyChatPublicationError( .filter((candidate): candidate is string => typeof candidate === "string") .map((candidate) => candidate.toLowerCase()); const reason = text(error); + if (names.includes("PhotonError")) { + if (codes.includes("delivery_unknown")) return { kind: "delivery_unknown", reason }; + if (codes.includes("credentials") || codes.includes("line_unavailable") || codes.includes("history_gap")) { + return { kind: "endpoint_attention", reason }; + } + if (codes.includes("quota") || codes.includes("network") || codes.includes("attachment_not_ready")) { + return { + kind: "retry", + retryAfterMs: values + .map((value) => finitePositive(value.retryAfterMs)) + .find((value) => value !== null) ?? Math.min(60_000, 1000 * 2 ** Math.min(attempt, 6)), + providerRateLimit: codes.includes("quota"), + reason, + }; + } + return { kind: "failed", reason }; + } const responseHeaders = values .map((value) => value.response?.headers) .find((headers) => headers !== undefined); diff --git a/server/src/services/chat-sdk-runtime.ts b/server/src/services/chat-sdk-runtime.ts index 486bb1ef56..2c6f96e534 100644 --- a/server/src/services/chat-sdk-runtime.ts +++ b/server/src/services/chat-sdk-runtime.ts @@ -1,3 +1,9 @@ +import { PhotonChatAdapter, parsePhotonThreadId } from "./photon/adapter.js"; +import { PhotonLineAuthentication } from "./photon/cloud.js"; +import { PhotonState } from "./photon/state.js"; +import { PhotonReceiver } from "./photon/receiver.js"; +import { photonAttachmentLocator, photonAttachmentLocatorSchema, downloadPhotonAttachment, type PhotonAttachmentLocator } from "./photon/attachments.js"; +import type { LiveEvent as PhotonEvent } from "@photon-ai/advanced-imessage"; import { createGitHubAdapter, type GitHubAdapter, @@ -130,8 +136,8 @@ const DISCORD_GATEWAY_HEALTHY_SESSION_MS = 60_000; /** Public Paperclip provider ids. The Teams SDK name remains an internal detail. */ export type ChatSdkProvider = - "slack" | "github" | "discord" | "microsoft-teams" | "telegram"; -type ChatSdkAdapterKey = "slack" | "github" | "discord" | "teams" | "telegram"; + "slack" | "github" | "discord" | "microsoft-teams" | "telegram" | "imessage-photon"; +type ChatSdkAdapterKey = "slack" | "github" | "discord" | "teams" | "telegram" | "imessage-photon"; interface ProviderConfigBase { /** Agent-derived native bot display/mention name. */ @@ -200,7 +206,13 @@ export interface ResolvedTelegramChatConfig extends ProviderConfigBase { }; } +export interface ResolvedPhotonChatConfig extends ProviderConfigBase { + provider: "imessage-photon"; + intakeAfter: number; + credentials: { allocation?: "dedicated" | "shared"; projectId: string; projectSecret: string; lineId: string; phoneNumber: string }; +} export type ResolvedChatSdkProviderConfig = + | ResolvedPhotonChatConfig | ResolvedSlackChatConfig | ResolvedGitHubChatConfig | ResolvedDiscordChatConfig @@ -222,6 +234,7 @@ interface DurableAttachmentMetadata { } type ChatSdkAttachmentLocator = + | PhotonAttachmentLocator | GitHubPublicAttachmentLocator | TeamsInlineImageLocator | TelegramMediaLocator @@ -453,6 +466,10 @@ export interface DiscordGatewayCallbackEvent extends ChatSdkCallbackEvent; + onPhotonAssertOwned?(activeThreadId?: string): Promise; + onPhotonCheckpoint?(sequence: number): Promise; + onPhotonFailure?(error: unknown): Promise; onMessage(event: ChatSdkMessageCallbackEvent): Promise | void; onTelegramGenerationStopped?( event: ChatSdkCallbackEvent, @@ -1339,6 +1356,7 @@ function createProviderAdapter( ): Adapter { const resolvedLogger = adapterLogger(logger); switch (config.provider) { + case "imessage-photon": throw new Error("Photon adapter requires scoped persistence"); case "slack": { const adapterConfig: SlackAdapterConfig = { ...config.credentials, @@ -2023,10 +2041,13 @@ export class ChatSdkEndpointRuntime { private discordGatewayFatal = false; private initialization: Promise | null = null; private retired = false; + private photonReceiver?: PhotonReceiver; + private readonly runtimeOptions: CreateChatSdkEndpointRuntimeOptions; private shutdownTask: Promise | null = null; private shutdownCompleted = false; constructor(options: CreateChatSdkEndpointRuntimeOptions) { + this.runtimeOptions = options; this.companyId = options.companyId; this.endpointId = options.endpointId; this.provider = options.providerConfig.provider; @@ -2076,7 +2097,11 @@ export class ChatSdkEndpointRuntime { 1, Math.min(options.webhookIngressTimeoutMs ?? 2_500, 10_000), ); - this.adapter = createProviderAdapter( + this.adapter = options.providerConfig.provider === "imessage-photon" + ? new PhotonChatAdapter(options.providerConfig.userName, + new PhotonLineAuthentication(options.providerConfig.credentials, options.providerConfig.credentials.projectSecret), + new PhotonState({ companyId: options.companyId, endpointId: options.endpointId }, options.persistence)) + : createProviderAdapter( options.providerConfig, options.logger, options.callbacks, @@ -2256,6 +2281,18 @@ export class ChatSdkEndpointRuntime { async initialize(): Promise { await this.initializeChat(); this.assertNotRetired(); + if (this.adapter instanceof PhotonChatAdapter && this.discordGatewayEnabled && !this.photonReceiver) { + const options = this.runtimeOptions; + const config = options.providerConfig as ResolvedPhotonChatConfig; + if (!options.callbacks.onPhotonCheckpoint || !options.callbacks.onPhotonAssertOwned || !options.callbacks.onPhotonEvent || !options.callbacks.onPhotonFailure) throw new Error("Photon receiver requires durable admission callbacks"); + this.adapter.typingGuard = async (activeThreadId) => { this.assertNotRetired(); await options.callbacks.onPhotonAssertOwned!(activeThreadId); }; + this.photonReceiver = new PhotonReceiver({ client: this.adapter.client, state: this.adapter.state, + lineId: config.credentials.lineId, intakeAfter: config.intakeAfter, allocation: config.credentials.allocation, + catchUp: (sequence) => (this.adapter as PhotonChatAdapter).recoveryStream(sequence), + assertOwned: async () => { this.assertNotRetired(); await (this.adapter as PhotonChatAdapter).authentication.token(); await options.callbacks.onPhotonAssertOwned!(); }, + commitCheckpoint: options.callbacks.onPhotonCheckpoint, admit: options.callbacks.onPhotonEvent, failure: options.callbacks.onPhotonFailure }); + this.photonReceiver.start(); + } if (this.provider === "discord" && this.discordGatewayEnabled) { this.startDiscordGateway(); } @@ -2706,6 +2743,12 @@ export class ChatSdkEndpointRuntime { const metadata = durableAttachmentMetadata(attachment); return locator && metadata ? { version: 1, provider: "telegram", attachment: metadata, locator } : null; } + if (this.adapter instanceof PhotonChatAdapter && source?.message) { + const thread = this.adapter.decodeThreadId(source.threadId); + const locator = photonAttachmentLocator(attachment, thread.lineId, thread.chatGuid, source.message); + const metadata = durableAttachmentMetadata(attachment); + return locator && metadata ? { version: 1, provider: "imessage-photon", attachment: metadata, locator } : null; + } const retained = this.teamsInlineImageDescriptors.get(attachment); if (retained) { if (!source) return retained; @@ -2875,6 +2918,14 @@ export class ChatSdkEndpointRuntime { descriptor: unknown, source?: ChatSdkAttachmentSource, ): Attachment | null { + if (this.adapter instanceof PhotonChatAdapter && isRecord(descriptor) && descriptor.version === 1 && descriptor.provider === this.provider && source) { + const parsed = photonAttachmentLocatorSchema.safeParse(descriptor.locator); + const thread = this.adapter.decodeThreadId(source.threadId); + const metadata = isRecord(descriptor.attachment) ? durableAttachmentMetadata(descriptor.attachment as unknown as Attachment) : null; + if (!parsed.success || !metadata || parsed.data.lineId !== thread.lineId || parsed.data.chatGuid !== thread.chatGuid || parsed.data.messageGuid !== source.messageId) return null; + const adapter = this.adapter; + return { ...metadata, fetchData: () => downloadPhotonAttachment(adapter.client, thread.lineId, parsed.data, adapter.authentication.identity.allocation) }; + } if ( this.provider === "microsoft-teams" && isRecord(descriptor) && @@ -3048,6 +3099,7 @@ export class ChatSdkEndpointRuntime { connectorOrigin: validated.locator.connectorOrigin, }; break; + case "photon_attachment": case "teams_inline_image": return null; // Only the exact source-bound branch above may authorize it. case "telegram_media": @@ -3086,6 +3138,7 @@ export class ChatSdkEndpointRuntime { await this.initialization?.catch(() => undefined); await this.discordGatewayTask?.catch(() => undefined); this.discordGatewayAbort = null; + await this.photonReceiver?.close(); await this.chat.shutdown(); this.shutdownCompleted = true; })(); diff --git a/server/src/services/codex-auth-reconciliation.ts b/server/src/services/codex-auth-reconciliation.ts index 3e286823a1..decfe765ac 100644 --- a/server/src/services/codex-auth-reconciliation.ts +++ b/server/src/services/codex-auth-reconciliation.ts @@ -90,11 +90,13 @@ export async function reconcileCodexLocalManagedHomesOnStartup( id: agents.id, companyId: agents.companyId, adapterConfig: agents.adapterConfig, + runtimeConfig: agents.runtimeConfig, }) .from(agents) .where(eq(agents.adapterType, "codex_local")); for (const row of rows) { + if (row.runtimeConfig?.aiConnection) continue; summary.scanned += 1; const env = asRecord(asRecord(row.adapterConfig)?.env); const configuredCodexHome = env ? readPlainEnvValue(env.CODEX_HOME) : null; diff --git a/server/src/services/company-search.ts b/server/src/services/company-search.ts index 4de7430cc9..96af85b6aa 100644 --- a/server/src/services/company-search.ts +++ b/server/src/services/company-search.ts @@ -15,7 +15,6 @@ import { import { COMPANY_SEARCH_MAX_LIMIT, COMPANY_SEARCH_MAX_OFFSET, - COMPANY_SEARCH_MAX_TOKENS, COMPANY_SEARCH_UPDATED_WITHIN_OPTIONS, COMPANY_ARTIFACTS_MAX_LIMIT, COMPANY_ARTIFACTS_MAX_QUERY_LENGTH, @@ -39,18 +38,8 @@ import { import { companyArtifactsService } from "./company-artifacts.js"; import { companySearchExtractService } from "./company-search-extract.js"; import { visibleIssueCondition } from "./issue-visibility.js"; +import { parseTaskSearch, taskSearchCtes, taskSearchScore, taskSearchFieldMatch, taskSearchTermMatch } from "./task-search.js"; -const MIN_TOKEN_LENGTH = 2; -const MIN_FUZZY_QUERY_LENGTH = 4; -const MIN_FUZZY_TOKEN_LENGTH = 4; -// Cap fuzzy edits using the shorter of (query token, title word) so common -// 4–5 letter English words don't sweep in noise (e.g. "serach" vs "each"). -const FUZZY_PAIR_LONG_LENGTH = 6; -const FUZZY_PAIR_LONG_MAX_EDITS = 2; -const FUZZY_PAIR_MEDIUM_LENGTH = 5; -const FUZZY_PAIR_MEDIUM_MAX_EDITS = 1; -const FUZZY_PAIR_SHORT_MAX_EDITS = 0; -const FUZZY_IDENTIFIER_SIMILARITY_THRESHOLD = 0.45; const SNIPPET_MAX_CHARS = 240; export const COMPANY_SEARCH_BRANCH_FETCH_LIMIT = COMPANY_SEARCH_MAX_OFFSET + COMPANY_SEARCH_MAX_LIMIT + 1; @@ -95,30 +84,6 @@ type SearchAggregateRow = { count: number | string; }; -function normalizeQuery(query: string) { - return query.trim().replace(/\s+/g, " ").toLowerCase(); -} - -function escapeLikePattern(value: string): string { - return value.replace(/[\\%_]/g, "\\$&"); -} - -function tokenizeQuery(normalizedQuery: string) { - const matches = normalizedQuery.match(/"[^"]+"|[^\s]+/g) ?? []; - const tokens: string[] = []; - for (const match of matches) { - const token = match.replace(/^"|"$/g, "").replace(/^[^\p{L}\p{N}%_\\-]+|[^\p{L}\p{N}%_\\-]+$/gu, ""); - if (token.length < MIN_TOKEN_LENGTH) continue; - if (!tokens.includes(token)) tokens.push(token); - if (tokens.length >= COMPANY_SEARCH_MAX_TOKENS) break; - } - return tokens; -} - -function fuzzyEligibleTokens(tokens: string[]): string[] { - return tokens.filter((token) => token.length >= MIN_FUZZY_TOKEN_LENGTH); -} - function sqlTextArray(values: string[]) { if (values.length === 0) return sql`ARRAY[]::text[]`; return sql`ARRAY[${sql.join(values.map((value) => sql`${value}`), sql`, `)}]::text[]`; @@ -138,7 +103,7 @@ function plainText(value: string | null | undefined) { .replace(/```[\s\S]*?```/g, " ") .replace(/`([^`]+)`/g, "$1") .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1") - .replace(/[#>*_~|]+/g, " ") + .replace(/(^|\s)[#>*_~|]+|[#>*_~|]+(?=\s|$)/g, " ") .replace(/\s+/g, " ") .trim(); } @@ -431,6 +396,8 @@ function scopeIncludesProjects(scope: CompanySearchScope) { function selectPrimarySnippets(row: IssueSearchRow, normalizedQuery: string, tokens: string[]) { const terms = matchTerms(normalizedQuery, tokens); + const identifierQuery = parseTaskSearch(normalizedQuery).identifierQuery; + if (!terms.includes(identifierQuery)) terms.push(identifierQuery); const matchedFields = new Set(row.matchedFields ?? []); const candidates: Array = []; if (matchedFields.has("identifier")) { @@ -439,15 +406,22 @@ function selectPrimarySnippets(row: IssueSearchRow, normalizedQuery: string, tok if (matchedFields.has("title")) { candidates.push(createSnippet("title", "Title", row.title, terms)); } - if (matchedFields.has("comment")) { - candidates.push(createSnippet("comment", "Comment", row.commentSnippet, terms)); - } - if (matchedFields.has("document")) { - candidates.push(createSnippet("document", row.documentTitle || "Document", row.documentSnippet, terms)); - } - if (matchedFields.has("description")) { - candidates.push(createSnippet("description", "Description", row.description, terms)); - } + const description = matchedFields.has("description") + ? createSnippet("description", "Description", row.description, terms) : null; + const directMatch = Number(row.score) >= 3000 || Number(row.score) < 2000; + if (directMatch) candidates.push(description); + const context = [ + { field: "comment", label: "Comment", text: row.commentSnippet }, + { field: "document", label: row.documentTitle || "Document", text: [row.documentTitle, row.documentSnippet].filter(Boolean).join(" ") }, + ].filter((source) => matchedFields.has(source.field)); + const coverage = (text: string | null) => tokens.filter((term) => (text ?? "").toLowerCase().includes(term)).length; + context.sort((left, right) => coverage(right.text) - coverage(left.text)); + const contextSnippets = context.map((source) => createSnippet(source.field, source.label, source.text, terms)); + // The title and identifier are already visible in the row. For thread-only + // coverage, preserve the evidence before applying the two-snippet limit. + if (directMatch) candidates.push(...contextSnippets); + else candidates.unshift(...contextSnippets); + if (!directMatch) candidates.push(description); return candidates.filter((snippet): snippet is CompanySearchSnippet => Boolean(snippet)).slice(0, 2); } @@ -456,7 +430,11 @@ function issueResult(row: IssueSearchRow, prefix: string, normalizedQuery: strin const sourceLabel = snippets[0]?.label ?? null; const documentSuffix = row.documentKey ? `#document-${encodeURIComponent(row.documentKey)}` : ""; const commentSuffix = row.commentId ? `#comment-${encodeURIComponent(row.commentId)}` : ""; - const suffix = row.commentId ? commentSuffix : documentSuffix; + // Direct task matches open the task; context matches open the evidence shown. + const directMatch = Number(row.score) >= 3000 || Number(row.score) < 2000; + const evidence = snippets.find((snippet) => snippet.field === "comment" || snippet.field === "document"); + const suffix = directMatch ? "" : evidence?.field === "comment" ? commentSuffix + : evidence?.field === "document" ? documentSuffix : ""; const issue: CompanySearchIssueSummary = { id: row.id, identifier: row.identifier, @@ -495,7 +473,9 @@ function scoreSimpleRow(row: SimpleSearchRow, normalizedQuery: string, tokens: s if (haystack.includes(token)) score += 20; } if (row.title.toLowerCase().startsWith(normalizedQuery)) score += 80; - return score; + // Keep other entity types on the same scale as the task relevance bands: + // an exact agent/project name must still outrank a speculative task typo. + return score * 10; } function artifactResult(artifact: CompanyArtifact, normalizedQuery: string, tokens: string[]): CompanySearchResult { @@ -559,9 +539,10 @@ export function companySearchService(db: Db) { return { extract: extractService.extract, search: async (companyId: string, query: CompanySearchQuery): Promise => { - const normalizedQuery = normalizeQuery(query.q); + const taskSearch = parseTaskSearch(query.q); + const normalizedQuery = taskSearch.normalizedQuery; const hasSearchText = normalizedQuery.length > 0; - const tokens = tokenizeQuery(normalizedQuery); + const tokens = taskSearch.tokens; const scope = query.scope; const sort = query.sort; const limit = query.limit; @@ -583,147 +564,20 @@ export function companySearchService(db: Db) { } const fetchLimit = companySearchBranchFetchLimit(limit, offset); - const escapedTokens = tokens.map(escapeLikePattern); - // LIKE/ILIKE both treat backslash as the default escape character, so the - // escaped tokens stay literal inside ILIKE ANY(...) patterns too. - const tokenPatterns = escapedTokens.map((token) => `%${token}%`); - const tokenPatternArray = sqlTextArray(tokenPatterns); - const fuzzyTokens = fuzzyEligibleTokens(tokens); - const fuzzyTokenArray = sqlTextArray(fuzzyTokens); - const escapedQuery = escapeLikePattern(normalizedQuery); - const containsPattern = hasSearchText ? `%${escapedQuery}%` : "__paperclip_no_match__"; - const startsWithPattern = hasSearchText ? `${escapedQuery}%` : "__paperclip_no_match__"; - const fuzzyEnabled = hasSearchText && normalizedQuery.length >= MIN_FUZZY_QUERY_LENGTH && !/[\\%_]/.test(normalizedQuery); - const fuzzyTokensEnabled = fuzzyEnabled && fuzzyTokens.length > 0; + const tokenPatternArray = sqlTextArray(taskSearch.patterns); + const containsPattern = hasSearchText && tokens.length > 0 ? taskSearch.containsPattern : "__paperclip_no_match__"; const tokenCount = tokens.length; - // --- shared match expressions against the `issues` table ------------- - // Raw-column ILIKE keeps the predicates compatible with the existing - // pg_trgm GIN indexes (lower(col) LIKE expressions cannot use them). - const titlePhraseMatch = hasSearchText ? sql`issues.title ILIKE ${containsPattern}` : noMatchSql(); - const titleStartsWith = hasSearchText ? sql`issues.title ILIKE ${startsWithPattern}` : noMatchSql(); - const titleExactMatch = hasSearchText ? sql`lower(issues.title) = ${normalizedQuery}` : noMatchSql(); - const identifierPhraseMatch = hasSearchText ? sql`coalesce(issues.identifier, '') ILIKE ${containsPattern}` : noMatchSql(); - const identifierStartsWith = hasSearchText ? sql`coalesce(issues.identifier, '') ILIKE ${startsWithPattern}` : noMatchSql(); - const identifierExactMatch = hasSearchText ? sql`lower(coalesce(issues.identifier, '')) = ${normalizedQuery}` : noMatchSql(); - const descriptionPhraseMatch = hasSearchText ? sql`coalesce(issues.description, '') ILIKE ${containsPattern}` : noMatchSql(); - const titleTokenMatch = tokenCount > 0 ? sql`issues.title ILIKE ANY(${tokenPatternArray})` : noMatchSql(); - const identifierTokenMatch = tokenCount > 0 ? sql`coalesce(issues.identifier, '') ILIKE ANY(${tokenPatternArray})` : noMatchSql(); - const descriptionTokenMatch = tokenCount > 0 ? sql`coalesce(issues.description, '') ILIKE ANY(${tokenPatternArray})` : noMatchSql(); - // Comment/document matches are computed once per request into tagged - // CTEs (issue_id, ord) where ord 1 is the phrase pattern and ord k+1 is - // token k. Flags and per-token coverage become cheap hashed IN probes - // against those sets instead of per-issue-row correlated subqueries. - // Single-pattern queries stay a bare `col ILIKE pattern` so the pg_trgm - // GIN indexes can bitmap-scan them; multi-pattern queries use one tagged - // pass over the table (an OR/ANY form would seq-scan anyway). - const matchPatterns = hasSearchText - ? [containsPattern, ...tokenPatterns.filter((pattern) => pattern !== containsPattern)] - : []; - const matchPatternOrdinal = (pattern: string) => matchPatterns.indexOf(pattern) + 1; - const matchPatternArray = sqlTextArray(matchPatterns); - const commentMatchesCte = !hasSearchText - ? sql`SELECT NULL::uuid AS issue_id, 0 AS ord WHERE false` - : matchPatterns.length === 1 - ? sql` - SELECT search_comments.issue_id, 1 AS ord - FROM issue_comments search_comments - WHERE search_comments.company_id = ${companyId} - AND search_comments.deleted_at IS NULL - AND search_comments.body ILIKE ${matchPatterns[0]!} - GROUP BY 1, 2 - ` - : sql` - SELECT search_comments.issue_id, pat.ord::int AS ord - FROM issue_comments search_comments - INNER JOIN unnest(${matchPatternArray}) WITH ORDINALITY AS pat(pattern, ord) - ON search_comments.body ILIKE pat.pattern - WHERE search_comments.company_id = ${companyId} - AND search_comments.deleted_at IS NULL - GROUP BY 1, 2 - `; - // Documents get one UNION ALL arm per pattern (each arm a bare - // `col ILIKE pattern`) so the planner can pick a pg_trgm bitmap scan per - // pattern; latest_body is large enough that skipping the seq scan for - // selective patterns dwarfs the duplicate-recheck cost on common ones. - const documentMatchesCte = !hasSearchText - ? sql`SELECT NULL::uuid AS issue_id, 0 AS ord WHERE false` - : sql.join(matchPatterns.map((pattern, index) => sql` - SELECT search_issue_documents.issue_id, ${index + 1}::int AS ord - FROM issue_documents search_issue_documents - INNER JOIN documents search_documents - ON search_documents.id = search_issue_documents.document_id - AND search_documents.company_id = search_issue_documents.company_id - WHERE search_issue_documents.company_id = ${companyId} - AND ( - search_documents.title ILIKE ${pattern} - OR search_documents.latest_body ILIKE ${pattern} - ) - GROUP BY 1, 2 - `), sql` UNION ALL `); - const commentMatch = hasSearchText - ? sql`issues.id IN (SELECT comment_matches.issue_id FROM comment_matches)` - : noMatchSql(); - const documentMatch = hasSearchText - ? sql`issues.id IN (SELECT document_matches.issue_id FROM document_matches)` - : noMatchSql(); - // Each query token (length >= MIN_FUZZY_TOKEN_LENGTH) must have at least - // one title word within Levenshtein edit distance. This handles typos - // like "serach" -> "search" (transposition) and "mibile" -> "mobile" - // (substitution) without the trigram noise that drop-character variants - // produced (e.g. "serac" matching "service"). Edit budget is gated on - // the SHORTER of the two strings so 4–5 letter English words don't get - // swept in by lev=2 collisions. - const fuzzyMaxEditsExpr = sql.raw( - `CASE - WHEN least(length(qt.value), length(title_word.value)) >= ${FUZZY_PAIR_LONG_LENGTH} THEN ${FUZZY_PAIR_LONG_MAX_EDITS} - WHEN least(length(qt.value), length(title_word.value)) >= ${FUZZY_PAIR_MEDIUM_LENGTH} THEN ${FUZZY_PAIR_MEDIUM_MAX_EDITS} - ELSE ${FUZZY_PAIR_SHORT_MAX_EDITS} - END`, - ); - const fuzzyMinTitleWordLengthExpr = sql.raw(`${MIN_FUZZY_TOKEN_LENGTH}`); - const fuzzyTokenTitleMatch = fuzzyTokensEnabled - ? sql` - coalesce(( - SELECT bool_and( - EXISTS ( - SELECT 1 - FROM regexp_split_to_table(lower(issues.title), '[^a-z0-9]+') AS title_word(value) - WHERE length(title_word.value) >= ${fuzzyMinTitleWordLengthExpr} - AND levenshtein_less_equal(qt.value, title_word.value, ${fuzzyMaxEditsExpr}) <= ${fuzzyMaxEditsExpr} - ) - ) - FROM unnest(${fuzzyTokenArray}) AS qt(value) - ), false) - ` - : noMatchSql(); - const fuzzyIdentifierMatch = fuzzyEnabled - ? sql`similarity(lower(coalesce(issues.identifier, '')), ${normalizedQuery}) >= ${FUZZY_IDENTIFIER_SIMILARITY_THRESHOLD}` - : noMatchSql(); - - const issueTextMatch = sql`( - ${titlePhraseMatch} - OR ${identifierPhraseMatch} - OR ${descriptionPhraseMatch} - OR ${titleTokenMatch} - OR ${identifierTokenMatch} - OR ${descriptionTokenMatch} - )`; - const fuzzyMatch = sql`(${fuzzyTokenTitleMatch} OR ${fuzzyIdentifierMatch})`; - const anySearchMatch = sql`(${issueTextMatch} OR ${commentMatch} OR ${documentMatch} OR ${fuzzyMatch})`; - const issueFilters = issueFilterConditions(companyId, query); const hasIssueOnlyFilters = issueOnlyFiltersActive(query); // Scope conditions over precomputed flag columns (alias-qualified). function flagTextMatch(alias: string) { - return sql`( - ${sql.raw(alias)}.title_phrase OR ${sql.raw(alias)}.ident_phrase OR ${sql.raw(alias)}.desc_phrase - OR ${sql.raw(alias)}.title_token OR ${sql.raw(alias)}.ident_token OR ${sql.raw(alias)}.desc_token - )`; + return sql`(${sql.raw(alias)}.issue_coverage = ${tokenCount} + OR ${sql.raw(alias)}.ident_exact OR ${sql.raw(alias)}.ident_starts)`; } function flagFuzzyMatch(alias: string) { - return sql`(${sql.raw(alias)}.fuzzy_title OR ${sql.raw(alias)}.fuzzy_ident)`; + return sql`${sql.raw(alias)}.fuzzy_title`; } function flagScopeCondition(alias: string, forScope: CompanySearchScope): SQL { if (!hasSearchText) { @@ -732,7 +586,7 @@ export function companySearchService(db: Db) { if (forScope === "comments") return sql`${sql.raw(alias)}.comment_match`; if (forScope === "documents") return sql`${sql.raw(alias)}.document_match`; if (forScope === "issues") return sql`(${flagTextMatch(alias)} OR ${flagFuzzyMatch(alias)})`; - return sql`(${flagTextMatch(alias)} OR ${sql.raw(alias)}.comment_match OR ${sql.raw(alias)}.document_match OR ${flagFuzzyMatch(alias)})`; + return sql`true`; } // --- combined issue results + aggregates statement --------------------- @@ -764,24 +618,7 @@ export function companySearchService(db: Db) { const wantResultRows = scopeIncludesIssues(scope) && !(!hasSearchText && (scope === "comments" || scope === "documents")); if (wantResultRows) { - const allTokensBonus = tokenCount > 0 - ? sql`CASE WHEN m.token_coverage = ${tokenCount} THEN 260 ELSE 0 END` - : sql`0`; - const scoreSql = sql`( - CASE WHEN m.ident_exact THEN 1200 ELSE 0 END - + CASE WHEN m.ident_starts THEN 700 ELSE 0 END - + CASE WHEN m.title_exact THEN 900 ELSE 0 END - + CASE WHEN m.title_starts THEN 550 ELSE 0 END - + CASE WHEN m.title_phrase THEN 350 ELSE 0 END - + CASE WHEN m.ident_phrase THEN 320 ELSE 0 END - + CASE WHEN m.comment_match THEN 180 ELSE 0 END - + CASE WHEN m.document_match THEN 170 ELSE 0 END - + CASE WHEN m.desc_phrase THEN 120 ELSE 0 END - + ${allTokensBonus} - + (m.token_coverage * 70) - + CASE WHEN (m.fuzzy_title OR m.fuzzy_ident) THEN 110 ELSE 0 END - + CASE m.status WHEN 'done' THEN 0 WHEN 'cancelled' THEN -30 ELSE 20 END - )::double precision`; + const scoreSql = taskSearchScore(taskSearch); const priorityOrderSql = sql`CASE m.priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 ELSE 4 END`; const orderBySql = sort === "updated" ? sql`m.updated_at DESC, score DESC, m.id DESC` @@ -798,7 +635,8 @@ export function companySearchService(db: Db) { m.id, m.identifier, m.title, - m.description, + (SELECT issue_text.description FROM issues issue_text + WHERE issue_text.id = m.id AND issue_text.company_id = ${companyId}) AS description, m.status, m.priority, m.assignee_agent_id AS "assigneeAgentId", @@ -808,9 +646,9 @@ export function companySearchService(db: Db) { m.updated_at AS "updatedAt", ${scoreSql} AS score, array_remove(ARRAY[ - CASE WHEN m.ident_phrase OR m.ident_token OR m.fuzzy_ident THEN 'identifier' END, - CASE WHEN m.title_phrase OR m.title_token OR m.fuzzy_title THEN 'title' END, - CASE WHEN m.desc_phrase OR m.desc_token THEN 'description' END, + CASE WHEN m.ident_exact OR m.ident_starts OR m.ident_phrase OR m.ident_token THEN 'identifier' END, + CASE WHEN m.title_token OR m.fuzzy_title THEN 'title' END, + CASE WHEN m.desc_token THEN 'description' END, CASE WHEN m.comment_match THEN 'comment' END, CASE WHEN m.document_match THEN 'document' END ], NULL)::text[] AS "matchedFields" @@ -825,10 +663,10 @@ export function companySearchService(db: Db) { branches.push(sql`SELECT 'type:issue' AS kind, NULL::text AS value, count(*)::int AS count ${countTail} FROM matched m ${branchWhere([...facetsAll, titleCond])}`); } if (hasSearchText && (scope === "all" || scope === "comments")) { - branches.push(sql`SELECT 'type:comment' AS kind, NULL::text AS value, count(*)::int AS count ${countTail} FROM matched m ${branchWhere([...facetsAll, sql`m.comment_match`])}`); + branches.push(sql`SELECT 'type:comment' AS kind, NULL::text AS value, count(*)::int AS count ${countTail} FROM matched m ${branchWhere([...facetsAll, flagScopeCondition("m", "comments")])}`); } if (hasSearchText && (scope === "all" || scope === "documents")) { - branches.push(sql`SELECT 'type:document' AS kind, NULL::text AS value, count(*)::int AS count ${countTail} FROM matched m ${branchWhere([...facetsAll, sql`m.document_match`])}`); + branches.push(sql`SELECT 'type:document' AS kind, NULL::text AS value, count(*)::int AS count ${countTail} FROM matched m ${branchWhere([...facetsAll, flagScopeCondition("m", "documents")])}`); } const facetBranch = (kind: string, valueSql: SQL, omit: CompanySearchIssueFilterKey, extra: SQL[] = []) => sql` @@ -878,59 +716,8 @@ export function companySearchService(db: Db) { } } - // Per-token coverage counts matches across issue text and the tagged - // comment/document match sets (hashed IN probes, one set per token). - const coverageSql = tokenCount > 0 - ? sql`(${sql.join(tokens.map((_, index) => { - const pattern = tokenPatterns[index]!; - const ord = matchPatternOrdinal(pattern); - return sql`(CASE WHEN - issues.title ILIKE ${pattern} - OR coalesce(issues.identifier, '') ILIKE ${pattern} - OR coalesce(issues.description, '') ILIKE ${pattern} - OR issues.id IN (SELECT comment_matches.issue_id FROM comment_matches WHERE comment_matches.ord = ${ord}) - OR issues.id IN (SELECT document_matches.issue_id FROM document_matches WHERE document_matches.ord = ${ord}) - THEN 1 ELSE 0 END)`; - }), sql` + `)})` - : sql`0`; - - const matchedWhere = hasSearchText ? sql` AND ${anySearchMatch}` : sql``; const resultRows = await db.execute(sql` - WITH comment_matches AS MATERIALIZED (${commentMatchesCte}), - document_matches AS MATERIALIZED (${documentMatchesCte}), - matched AS MATERIALIZED ( - SELECT - issues.id, - issues.identifier, - issues.title, - issues.description, - issues.status, - issues.priority, - issues.assignee_agent_id, - issues.assignee_user_id, - issues.project_id, - issues.created_at, - issues.updated_at, - ${titlePhraseMatch} AS title_phrase, - ${titleStartsWith} AS title_starts, - ${titleExactMatch} AS title_exact, - ${identifierPhraseMatch} AS ident_phrase, - ${identifierStartsWith} AS ident_starts, - ${identifierExactMatch} AS ident_exact, - ${descriptionPhraseMatch} AS desc_phrase, - ${titleTokenMatch} AS title_token, - ${identifierTokenMatch} AS ident_token, - ${descriptionTokenMatch} AS desc_token, - ${commentMatch} AS comment_match, - ${documentMatch} AS document_match, - ${fuzzyTokenTitleMatch} AS fuzzy_title, - ${fuzzyIdentifierMatch} AS fuzzy_ident, - ${coverageSql} AS token_coverage - FROM issues - WHERE issues.company_id = ${companyId} - AND ${visibleIssueCondition()} - ${matchedWhere} - ) + ${taskSearchCtes(companyId, taskSearch, scope !== "issues", and(...issueFilters))} ${sql.join(branches, sql` UNION ALL `)} `) as unknown as Array>; @@ -1014,11 +801,11 @@ export function companySearchService(db: Db) { AND search_comments.issue_id = target.id AND search_comments.deleted_at IS NULL AND ( - search_comments.body ILIKE ${containsPattern} - OR search_comments.body ILIKE ANY(${tokenPatternArray}) + ${taskSearchFieldMatch(sql`search_comments.body`, taskSearch)} ) ORDER BY CASE WHEN search_comments.body ILIKE ${containsPattern} THEN 0 ELSE 1 END, + ${sql.join(tokens.map((_, index) => sql`CASE WHEN ${taskSearchTermMatch(sql`search_comments.body`, taskSearch, index)} THEN 1 ELSE 0 END`), sql` + `)} DESC, search_comments.updated_at DESC, search_comments.id DESC LIMIT 1 @@ -1032,10 +819,8 @@ export function companySearchService(db: Db) { WHERE search_issue_documents.company_id = ${companyId} AND search_issue_documents.issue_id = target.id AND ( - coalesce(search_documents.title, '') ILIKE ${containsPattern} - OR search_documents.latest_body ILIKE ${containsPattern} - OR coalesce(search_documents.title, '') ILIKE ANY(${tokenPatternArray}) - OR search_documents.latest_body ILIKE ANY(${tokenPatternArray}) + ${taskSearchFieldMatch(sql`search_documents.title`, taskSearch)} + OR ${taskSearchFieldMatch(sql`search_documents.latest_body`, taskSearch)} ) ORDER BY CASE @@ -1043,6 +828,7 @@ export function companySearchService(db: Db) { WHEN search_documents.latest_body ILIKE ${containsPattern} THEN 1 ELSE 2 END, + ${sql.join(tokens.map((_, index) => sql`CASE WHEN ${taskSearchTermMatch(sql`search_documents.title`, taskSearch, index)} OR ${taskSearchTermMatch(sql`search_documents.latest_body`, taskSearch, index)} THEN 1 ELSE 0 END`), sql` + `)} DESC, search_documents.updated_at DESC, search_documents.id DESC LIMIT 1 diff --git a/server/src/services/connection-credential-bindings.ts b/server/src/services/connection-credential-bindings.ts new file mode 100644 index 0000000000..b6fb5a9aa4 --- /dev/null +++ b/server/src/services/connection-credential-bindings.ts @@ -0,0 +1,113 @@ +import { and, eq, inArray } from "drizzle-orm"; +import { type Db, toolConnections, companySecretBindings, connectionGrants, companySecrets, userSecretDefinitions } from "@paperclipai/db"; +import type { ToolCredentialSecretRef } from "@paperclipai/shared"; +import { secretService } from "./secrets.js"; +function credentialRefConfigPath(ref: { name: string }): string { return ref.name.startsWith("credentials.") ? ref.name : `credentials.${ref.name}`; } +export async function syncConnectionCredentialBindings( + db: Db | Parameters[0]>[0], + connection: typeof toolConnections.$inferSelect, + grantSecretRefs: ToolCredentialSecretRef[] = [], + dbClient: Pick = db, + ) { + const secrets = secretService(db); + await dbClient + .delete(companySecretBindings) + .where( + and( + eq(companySecretBindings.companyId, connection.companyId), + eq(companySecretBindings.targetType, "tool_connection"), + eq(companySecretBindings.targetId, connection.id), + ), + ); + // A metadata edit or pause/resume must retain declarations for every + // active personal/dedicated grant, not just connection-owned credentials. + const activeGrants = await dbClient.select({ refs: connectionGrants.credentialSecretRefs }) + .from(connectionGrants).where(and( + eq(connectionGrants.companyId, connection.companyId), + eq(connectionGrants.connectionId, connection.id), + eq(connectionGrants.status, "active"), + )); + const rawBindings = [ + ...connection.credentialRefs.map((ref) => ({ + secretId: ref.secretId, + configPath: credentialRefConfigPath(ref), + projectionClass: "unclassified", + projectionAllowlistKey: null, + required: true, + label: null, + })), + ...[...connection.credentialSecretRefs, ...grantSecretRefs, ...activeGrants.flatMap((grant) => grant.refs)].map((ref) => ({ + secretId: ref.secretId, + configPath: ref.configPath, + projectionClass: ref.projectionClass ?? "unclassified", + projectionAllowlistKey: ref.projectionAllowlistKey ?? null, + required: ref.required ?? true, + label: ref.label ?? null, + })), + ]; + // Organization grants can mirror connection-owned credentials, and more + // than one personal grant can reference the same client registration. + // Binding rows are unique per secret/config path, so collapse those mirrors + // before replacing the durable projection declarations. + const bindings = [...new Map(rawBindings.map((ref) => [ + `${ref.secretId}:${ref.configPath}`, + ref, + ])).values()]; + const secretRows = bindings.length > 0 + ? await dbClient.select({ + id: companySecrets.id, + scope: companySecrets.scope, + userSecretDefinitionId: companySecrets.userSecretDefinitionId, + }).from(companySecrets).where(and( + eq(companySecrets.companyId, connection.companyId), + inArray(companySecrets.id, [...new Set(bindings.map((ref) => ref.secretId))]), + )) + : []; + const secretById = new Map(secretRows.map((row) => [row.id, row])); + const definitionIds = [...new Set(secretRows.flatMap((row) => row.userSecretDefinitionId ? [row.userSecretDefinitionId] : []))]; + const definitions = definitionIds.length > 0 + ? await dbClient.select({ id: userSecretDefinitions.id, key: userSecretDefinitions.key }) + .from(userSecretDefinitions) + .where(and( + eq(userSecretDefinitions.companyId, connection.companyId), + inArray(userSecretDefinitions.id, definitionIds), + )) + : []; + const definitionKeyById = new Map(definitions.map((row) => [row.id, row.key])); + const userDeclarations = [...new Map(bindings.flatMap((ref) => { + const secret = secretById.get(ref.secretId); + const definitionKey = secret?.scope === "user" && secret.userSecretDefinitionId + ? definitionKeyById.get(secret.userSecretDefinitionId) + : null; + return definitionKey + ? [{ + definitionKey, + configPath: ref.configPath, + envKey: ref.configPath, + versionSelector: "latest" as const, + required: ref.required, + label: ref.label, + }] + : []; + }).map((ref) => [`${ref.definitionKey}:${ref.configPath}`, ref])).values()]; + await secrets.syncUserSecretDeclarationsForTarget( + connection.companyId, + { targetType: "tool_connection", targetId: connection.id }, + userDeclarations, + { replaceAll: true, db: dbClient }, + ); + const companyBindings = bindings.filter((ref) => secretById.get(ref.secretId)?.scope !== "user"); + if (companyBindings.length === 0) return; + await dbClient.insert(companySecretBindings).values(companyBindings.map((ref) => ({ + companyId: connection.companyId, + secretId: ref.secretId, + targetType: "tool_connection" as const, + targetId: connection.id, + configPath: ref.configPath, + required: ref.required, + label: ref.label, + projectionClass: ref.projectionClass, + projectionAllowlistKey: ref.projectionAllowlistKey, + }))); + } + diff --git a/server/src/services/connection-intent-delivery.ts b/server/src/services/connection-intent-delivery.ts index bfec2ad138..c056622ae0 100644 --- a/server/src/services/connection-intent-delivery.ts +++ b/server/src/services/connection-intent-delivery.ts @@ -1,7 +1,10 @@ import { connectionIntentService } from "./connection-intents.js"; -import { and, eq, isNull, lte, asc, notInArray } from "drizzle-orm"; -import { connectionIntentDeliveries, issueThreadInteractions, issues, agentWakeupRequests, companyMemberships, type Db } from "@paperclipai/db"; +import { and, eq, isNull, lte, asc, notInArray, desc, sql } from "drizzle-orm"; +import { connectionIntentDeliveries, issueThreadInteractions, issues, agentWakeupRequests, companyMemberships, heartbeatRuns, chatConversations, chatEndpoints, type Db } from "@paperclipai/db"; import type { heartbeatService } from "./heartbeat.js"; +import { issueService } from "./issues.js"; +import { issueRecoveryActionService } from "./issue-recovery-actions.js"; +import { logActivity, publishActivity, type ActivityPublication } from "./activity-log.js"; type Heartbeat = ReturnType; export async function wakeConnectionIntentAfterResolution( @@ -56,6 +59,51 @@ export async function wakeConnectionIntentAfterResolution( export function connectionIntentDeliveryService(db: Db, heartbeat: Pick) { + // Only a repaired AI-authentication failure may reopen a blocked task. An old + // card must never resume a newer failure, a reassignment, or a manual hold. + async function restoreAiBlockedTask(loaded: { + issue: typeof issues.$inferSelect; + interaction: typeof issueThreadInteractions.$inferSelect; + }) { + const publications: ActivityPublication[] = []; + const restored = await db.transaction(async (tx) => { + const [issue] = await tx.select().from(issues).where(and( + eq(issues.id, loaded.issue.id), eq(issues.companyId, loaded.issue.companyId), + )).for("update"); + if (!issue || issue.status !== "blocked" || issue.assigneeAgentId !== loaded.issue.assigneeAgentId) return null; + const [latest] = await tx.select().from(heartbeatRuns).where(and( + eq(heartbeatRuns.companyId, issue.companyId), + sql`coalesce(${heartbeatRuns.contextSnapshot}->>'issueId', ${heartbeatRuns.contextSnapshot}->>'taskId') = ${issue.id}`, + )).orderBy(desc(heartbeatRuns.createdAt)).limit(1); + const gap = latest?.resultJson?.configurationIncomplete as { reason?: string } | undefined; + if (latest?.id !== loaded.interaction.sourceRunId || latest.status !== "failed" + || latest.errorCode !== "configuration_incomplete" || gap?.reason !== "ai_connection_unavailable") return null; + // Restricted external chat retries require their original chat provenance. + // Their existing Try again path owns that authorization and delivery. + const [restrictedChat] = await tx.select({ id: chatConversations.id }).from(chatConversations) + .innerJoin(chatEndpoints, eq(chatEndpoints.id, chatConversations.endpointId)) + .where(and(eq(chatConversations.companyId, issue.companyId), eq(chatConversations.issueId, issue.id), + eq(chatEndpoints.externalExecutionPolicy, "restricted"))).limit(1); + if (restrictedChat) return null; + const recoveries = issueRecoveryActionService(db); + const recovery = await recoveries.getActiveForIssue(issue.companyId, issue.id, tx); + if (!recovery || recovery.cause !== "configuration_incomplete" || recovery.evidence.latestRunId !== latest.id) return null; + const actorId = loaded.interaction.resolvedByUserId ?? loaded.interaction.addresseeUserId!; + const updated = await issueService(db).update(issue.id, { + status: "in_progress", actorUserId: actorId, companyGuard: issue.companyId, + }, tx, publications); + await recoveries.resolveActiveForIssue({ companyId: issue.companyId, sourceIssueId: issue.id, + actionId: recovery.id, status: "resolved", outcome: "restored", resolutionNote: "AI connection restored by the responsible user." }, tx); + return updated; + }); + for (const publication of publications) publishActivity(publication); + if (restored) await logActivity(db, { companyId: restored.companyId, actorType: "user", + actorId: loaded.interaction.resolvedByUserId ?? loaded.interaction.addresseeUserId!, + action: "issue.updated", entityType: "issue", entityId: restored.id, + details: { status: restored.status, _previous: { status: "blocked" }, source: "ai_connection_restored", interactionId: loaded.interaction.id } }); + return restored; + } + async function deliver(interactionId: string) { // Deterministic acceptance-test failpoint: preserve committed outcomes across a server restart. if (process.env.NODE_ENV === "test" && process.env.PAPERCLIP_TEST_CONNECTION_DELIVERY_HOLD === "1") return; @@ -69,7 +117,7 @@ export function connectionIntentDeliveryService(db: Db, heartbeat: Pick db.select({ id: agentWakeupRequests.id }).from(agentWakeupRequests).where(and( eq(agentWakeupRequests.companyId, claimed.companyId), diff --git a/server/src/services/connection-intents.ts b/server/src/services/connection-intents.ts index 11667a7faa..3c975ebfc9 100644 --- a/server/src/services/connection-intents.ts +++ b/server/src/services/connection-intents.ts @@ -1,3 +1,6 @@ +import { logActivity } from "./activity-log.js"; +import { aiConnectionService } from "./ai-connections.js"; +import { aiConnectionBindingSchema } from "@paperclipai/shared"; import { and, eq } from "drizzle-orm"; import type { Db } from "@paperclipai/db"; import { @@ -64,7 +67,7 @@ function availableToolConnectionMethods( app: (typeof CONNECTABLE_APP_DEFINITIONS)[number], ) { return getAvailableConnectionMethods(app).filter( - (method) => (method.purpose ?? "tool") === "tool", + (method) => (method.purpose ?? "tool") === "tool" && method.transport !== "runtime_auth", ); } @@ -207,17 +210,32 @@ export function connectionIntentService(db: Db) { }; } + async function managedAgent(companyId: string, agentId: string, serviceSlug: string) { + const [agent] = await db.select().from(agents).where(and(eq(agents.companyId, companyId), eq(agents.id, agentId))); + const binding = aiConnectionBindingSchema.safeParse(agent?.runtimeConfig?.aiConnection).data; + return agent && binding?.provider === serviceSlug ? { agent, binding } : null; + } + async function usableConnectionForAgent(input: { companyId: string; agentId: string; responsibleUserId: string; serviceSlug: string; + purpose?: "ai"; inventory?: Awaited>; }) { + const managed = input.purpose === "ai" ? await managedAgent(input.companyId, input.agentId, input.serviceSlug) : null; + if (managed) { + try { + const selected = await aiConnectionService(db).select({ companyId: input.companyId, agentId: input.agentId, userId: input.responsibleUserId, adapterType: managed.agent.adapterType, model: managed.agent.adapterConfig.model, runnerProvider: managed.agent.adapterConfig.provider, acpxAgent: managed.agent.adapterConfig.acpxAgent, binding: managed.binding }); + return access.getConnection(selected.connection.id, input.companyId); + } catch (error) { if ([403, 404, 422].includes((error as { status?: number }).status ?? 0)) return null; throw error; } + } + if (input.purpose === "ai") return null; const inventory = input.inventory ?? await connectionInventory(input.companyId); const matching = inventory.connections.filter((connection) => sourceSlugForConnection(connection, inventory.applicationsById) === input.serviceSlug - && connection.status !== "archived" + && connection.status !== "archived" && connection.connectionPurpose !== "ai" ); if (matching.length === 0) return null; const effective = await access.getEffectiveProfilesForAgent(input.companyId, input.agentId); @@ -284,14 +302,15 @@ export function connectionIntentService(db: Db) { )); } - async function resolveService(service: string, companyId: string, userId: string, agentId: string) { + async function resolveService(service: string, companyId: string, userId: string, agentId: string, purpose?: "ai") { if (!service.startsWith("connection:")) { const app = getAppStoreDefinition(service); if (!app) throw notFound("Connection service was not found"); + const methods = purpose === "ai" ? getAvailableConnectionMethods(app).filter(method => method.transport === "runtime_auth") : availableToolConnectionMethods(app); return { ...app, available: app.availability?.available !== false, - searchCapabilities: availableToolConnectionMethods(app).map((method) => + searchCapabilities: methods.map((method) => `${method.whenToUse} ${method.capabilityProfile?.label ?? ""} ${method.capabilityProfile?.description ?? ""}`).join(" "), - methods: availableToolConnectionMethods(app).map((method) => ({ + methods: methods.map((method) => ({ key: method.key, label: method.label ?? method.key, auth: method.auth, })), source: "catalog" as const }; } @@ -300,6 +319,7 @@ export function connectionIntentService(db: Db) { throw notFound("Configured connection was not found"); } const connection = await access.getConnection(id, companyId); + if (connection.connectionPurpose === "ai" && purpose !== "ai") throw notFound("AI authentication is not a tool connection"); const { grants } = await access.listConnectionGrants(id, companyId); if (connection.status === "archived" || !grants.some((grant) => grant.status === "active" && ( grant.kind === "organization" || (grant.kind === "user" && grant.subjectUserId === userId) @@ -322,7 +342,7 @@ export function connectionIntentService(db: Db) { const tokens = normalized.split(/[^\p{L}\p{N}]+/u).filter(Boolean); const inventory = await connectionInventory(run.companyId); const candidates: Array<{ item: ConnectionSearchResultItem; score: number }> = []; - const services = [...APP_STORE_DEFINITIONS.map((app) => app.slug), + const services = [...APP_STORE_DEFINITIONS.filter(app => getAvailableConnectionMethods(app).some(method => method.transport !== "runtime_auth")).map((app) => app.slug), ...inventory.connections.filter((connection) => sourceSlugForConnection(connection, inventory.applicationsById)?.startsWith("connection:") && connection.status !== "archived").map((connection) => `connection:${connection.id}`)]; @@ -331,7 +351,7 @@ export function connectionIntentService(db: Db) { try { app = await resolveService(service, run.companyId, run.responsibleUserId!, agent.id); } catch (error) { if (service.startsWith("connection:") && (error as { status?: number }).status === 404) continue; throw error; } const matching = inventory.connections.filter((connection) => - sourceSlugForConnection(connection, inventory.applicationsById) === service && connection.status !== "archived"); + sourceSlugForConnection(connection, inventory.applicationsById) === service && connection.status !== "archived" && connection.connectionPurpose !== "ai"); // Indexed descriptions can contain private workspace metadata, including // for catalog providers. Check each configured connection's audience first. const catalogs = await Promise.all(matching.map(async (connection) => { @@ -367,9 +387,10 @@ export function connectionIntentService(db: Db) { async function request( claims: ConnectionRunClaims, serviceSlug: string, + options: { purpose?: "ai" } = {}, ): Promise { const context = await loadRunContext(claims); - const app = await resolveService(serviceSlug, context.run.companyId, context.run.responsibleUserId!, context.agent.id); + const app = await resolveService(serviceSlug, context.run.companyId, context.run.responsibleUserId!, context.agent.id, options.purpose); if (!app.available || app.methods.length === 0) { throw unprocessable(`Connection service ${serviceSlug} is not available`); } @@ -378,6 +399,7 @@ export function connectionIntentService(db: Db) { agentId: context.agent.id, responsibleUserId: context.run.responsibleUserId!, serviceSlug: app.slug, + purpose: options.purpose, }); if (ready) { return { @@ -386,16 +408,16 @@ export function connectionIntentService(db: Db) { state: "ready", connectionId: ready.id, interactionId: null, - instruction: `${app.name} is connected. Use its installed tools; a native continuation will refresh tools if needed.`, + instruction: options.purpose === "ai" ? `${app.name} authentication is available for the next execution.` : `${app.name} is connected. Use its installed tools; a native continuation will refresh tools if needed.`, }; } - if (await administrativeDenial(context.run.companyId, context.agent.id, app.slug, await connectionInventory(context.run.companyId))) { + if (options.purpose !== "ai" && await administrativeDenial(context.run.companyId, context.agent.id, app.slug, await connectionInventory(context.run.companyId))) { throw forbidden("This agent has no permitted actions for this service. Ask an administrator to review tool permissions; reconnecting will not remove a denial."); } const outcomeId = context.run.contextSnapshot?.interactionId; if (typeof outcomeId === "string") { const [outcome] = await db.select().from(issueThreadInteractions).where(and(eq(issueThreadInteractions.id, outcomeId), eq(issueThreadInteractions.companyId, context.run.companyId), eq(issueThreadInteractions.issueId, context.issue.id))); - if (outcome?.kind === "connection_intent" && outcome.status === "rejected" && connectionIntentPayloadSchema.parse(outcome.payload).serviceSlug === app.slug) { + if (outcome?.kind === "connection_intent" && outcome.status === "rejected" && connectionIntentPayloadSchema.parse(outcome.payload).serviceSlug === app.slug && connectionIntentPayloadSchema.parse(outcome.payload).purpose === options.purpose) { throw conflict("The user declined this connection. Pursue alternatives; do not request it again in this continuation."); } } @@ -405,6 +427,7 @@ export function connectionIntentService(db: Db) { payload: { version: 1, serviceSlug: app.slug, + ...(options.purpose ? { purpose: options.purpose } : {}), serviceName: app.name, serviceLogoUrl: app.branding.logoUrl ?? null, serviceDarkLogoUrl: app.branding.darkLogoUrl ?? null, @@ -415,10 +438,16 @@ export function connectionIntentService(db: Db) { sourceRunId: context.run.id, sourceIdentityContextId: context.run.activeIdentityContextId, addresseeUserId: context.run.responsibleUserId!, - idempotencyKey: `connection-intent:${context.run.id}:${context.run.responsibleUserId}:${app.slug}`, + idempotencyKey: `connection-intent:${context.run.id}:${context.run.responsibleUserId}:${app.slug}${options.purpose ? ":ai" : ""}`, }, ); if (interaction.status !== "pending") throw conflict("This connection request has already been resolved. Follow its recorded outcome."); + await logActivity(db, { + companyId: context.run.companyId, actorType: "agent", actorId: context.agent.id, + agentId: context.agent.id, runId: context.run.id, + action: "issue.thread_interaction_created", entityType: "issue", entityId: context.issue.id, + details: { interactionId: interaction.id, interactionKind: "connection_intent", purpose: options.purpose }, + }); return { version: 1, service: app.slug, @@ -441,24 +470,40 @@ export function connectionIntentService(db: Db) { return { ...row, interaction }; } - async function setupOptions(interactionId: string): Promise { + async function setupOptions(interactionId: string, options: { canManageOrganizationGrant?: boolean } = {}): Promise { const loaded = await loadIntent(interactionId); const payload = connectionIntentPayloadSchema.parse(loaded.interaction.payload); - const app = await resolveService(payload.serviceSlug, loaded.issue.companyId, loaded.interaction.addresseeUserId!, payload.requestingAgentId); + const app = await resolveService(payload.serviceSlug, loaded.issue.companyId, loaded.interaction.addresseeUserId!, payload.requestingAgentId, payload.purpose); + const managed = payload.purpose === "ai" ? await managedAgent(loaded.issue.companyId, payload.requestingAgentId, app.slug) : null; + if (payload.purpose === "ai" && !managed) throw conflict("The agent’s AI configuration changed. Start a new execution."); const inventory = await connectionInventory(loaded.issue.companyId); + const usableAiConnection = managed ? await usableConnectionForAgent({ + companyId: loaded.issue.companyId, agentId: payload.requestingAgentId, + responsibleUserId: loaded.interaction.addresseeUserId!, serviceSlug: app.slug, purpose: "ai", + }) : null; + const aiAccounts = managed ? await aiConnectionService(db).list(loaded.issue.companyId, loaded.interaction.addresseeUserId!) : []; + const selectedAiAccount = managed ? aiAccounts.find((account) => + account.provider === managed.binding.provider && account.method === managed.binding.method + && (managed.binding.mode === "responsible_user" ? account.isDefault + : account.id === managed.binding.connectionId && account.grantId === managed.binding.grantId) + ) : undefined; + const selectedAiGrant = selectedAiAccount + ? (await access.listConnectionGrants(selectedAiAccount.id, loaded.issue.companyId)).grants.find(grant => grant.id === selectedAiAccount.grantId) + : undefined; const matchingConnections = inventory.connections.filter((connection) => sourceSlugForConnection(connection, inventory.applicationsById) === app.slug && connection.status === "active" && connection.enabled ); const existingConnections = (await Promise.all(matchingConnections.map(async (connection) => { + if (managed) return connection.id === usableAiConnection?.id ? connection : null; const { grants } = await access.listConnectionGrants(connection.id, loaded.issue.companyId); const eligible = grants.some((grant) => grant.status === "active" && (grant.kind === "organization" || grant.subjectUserId === loaded.interaction.addresseeUserId || (grant.kind === "agent" && grant.subjectAgentId === payload.requestingAgentId)) ); - return eligible ? connection : null; + return eligible && connection.connectionPurpose !== "ai" ? connection : null; }))).filter((connection): connection is ToolConnection => connection !== null); return { version: 1, @@ -477,6 +522,14 @@ export function connectionIntentService(db: Db) { id, applicationId, name, status, enabled, })), requestedAgentId: payload.requestingAgentId, + aiConnection: managed?.binding, + aiRepair: selectedAiAccount ? { + connection: selectedAiAccount, + canReconnect: selectedAiGrant?.createdByUserId === loaded.interaction.addresseeUserId + && (selectedAiAccount.ownership === "personal" + ? selectedAiAccount.ownerUserId === loaded.interaction.addresseeUserId + : options.canManageOrganizationGrant === true), + } : undefined, }; } @@ -533,6 +586,23 @@ export function connectionIntentService(db: Db) { throw conflict("Finish and test this connection before using it for the task"); } + if (payload.purpose === "ai" && selectedConnection.connectionPurpose !== "ai") throw conflict("Select an AI account for this authentication request"); + if (selectedConnection.connectionPurpose === "ai") { + if (payload.purpose !== "ai") throw conflict("AI authentication cannot satisfy a tool connection request"); + const managed = await managedAgent(loaded.issue.companyId, payload.requestingAgentId, payload.serviceSlug); + if (!managed) throw conflict("Configure the agent’s AI connection before using this account"); + const service = aiConnectionService(txDb); + if (managed.binding.mode === "responsible_user") { + const selected = await service.select({ companyId: loaded.issue.companyId, agentId: payload.requestingAgentId, userId, adapterType: managed.agent.adapterType, model: managed.agent.adapterConfig.model, runnerProvider: managed.agent.adapterConfig.provider, acpxAgent: managed.agent.adapterConfig.acpxAgent, binding: managed.binding, allowUninstalledPersonal: true }); + if (selected.connection.id !== selectedConnection.id) throw conflict("Choose this account as your personal default in Connections first"); + const installs = await txAccess.listConnectionInstalls(selectedConnection.id, loaded.issue.companyId); + await txAccess.putConnectionInstalls(selectedConnection.id, { installs: [...installs, { targetType: "agent", targetId: payload.requestingAgentId }] }, { actorType: "user", actorId: userId }); + } + const selected = await service.select({ companyId: loaded.issue.companyId, agentId: payload.requestingAgentId, userId, adapterType: managed.agent.adapterType, model: managed.agent.adapterConfig.model, runnerProvider: managed.agent.adapterConfig.provider, acpxAgent: managed.agent.adapterConfig.acpxAgent, binding: managed.binding }); + if (selected.connection.id !== selectedConnection.id) throw conflict("This is not the account selected for the agent"); + return txInteractions.resolveConnectionIntent(loaded.issue, interactionId, { version: 1, outcome: "connected", connectionId: selected.connection.id }, { userId }); + } + let { grants } = await txAccess.listConnectionGrants( selectedConnection.id, loaded.issue.companyId, diff --git a/server/src/services/device-login-service.ts b/server/src/services/device-login-service.ts index 0ce7f0dce8..a76d46f179 100644 --- a/server/src/services/device-login-service.ts +++ b/server/src/services/device-login-service.ts @@ -208,6 +208,7 @@ export interface LoginSessionActivityEvent { export type LoginSessionActivityRecorder = (event: LoginSessionActivityEvent) => void; export interface StartDeviceLoginInput { + aiConnection?: import("@paperclipai/shared").AiConnectionLoginIntent; companyId: string; environmentId: string; adapterType: AgentAdapterType; @@ -262,6 +263,7 @@ export class AdapterAuthSessionConflictError extends Error { // --------------------------------------------------------------------------- export interface AdapterAuthSessionRow { + aiConnection?: import("@paperclipai/shared").AiConnectionLoginIntent; id: string; /** The public, CSPRNG session identifier. The API returns and looks up this * value. It never equals the internal primary-key `id`, so a caller cannot @@ -286,6 +288,7 @@ export interface AdapterAuthSessionRow { } export interface InsertAdapterAuthSessionInput { + aiConnection?: import("@paperclipai/shared").AiConnectionLoginIntent; id: string; /** The public, CSPRNG session identifier. The service builds it and returns it * to the client; the store persists it in `public_session_id`. */ @@ -486,6 +489,7 @@ function isUniqueViolation(error: unknown): boolean { function toRow(row: typeof adapterAuthSessions.$inferSelect): AdapterAuthSessionRow { return { id: row.id, + ...(row.aiConnection ? { aiConnection: row.aiConnection } : {}), publicSessionId: row.publicSessionId, companyId: row.companyId, environmentId: row.environmentId, @@ -534,6 +538,7 @@ export function createDbAdapterAuthSessionStore( environmentId: input.environmentId, adapterType: input.adapterType, startedByUserId: input.startedByUserId, + aiConnection: input.aiConnection, // The unified table requires a unique public session id. The service // builds it from a CSPRNG and returns it to the client, so the store // persists that value here. It never uses the internal id, a timestamp, @@ -934,6 +939,7 @@ export function createDeviceLoginService(deps: DeviceLoginServiceDeps) { environmentId: input.environmentId, adapterType: input.adapterType, startedByUserId: input.startedByUserId, + aiConnection: input.aiConnection, expiresAt, at: startedAt, }); @@ -1307,6 +1313,7 @@ export function createDeviceLoginService(deps: DeviceLoginServiceDeps) { const prompt = isOwner ? promptsBySession.get(row.id) ?? null : null; return { sessionId: row.publicSessionId, + ...(isOwner && row.aiConnection ? { aiConnection: row.aiConnection } : {}), environmentId: row.environmentId, status, expiresAt: row.expiresAt?.toISOString() ?? null, diff --git a/server/src/services/execution-continuation.ts b/server/src/services/execution-continuation.ts index 2b4b694c2e..b6dde3f9b1 100644 --- a/server/src/services/execution-continuation.ts +++ b/server/src/services/execution-continuation.ts @@ -1,4 +1,5 @@ -import { and, asc, desc, eq, isNotNull, isNull, sql } from "drizzle-orm"; +import { and, asc, desc, eq, inArray, isNotNull, isNull, sql } from "drizzle-orm"; +import { z } from "zod"; import { agentWakeupRequests, heartbeatRuns, @@ -11,6 +12,7 @@ import { import type { ExecutionContinuationEnvelope } from "@paperclipai/shared"; import { sanitizeQuarantinedCommentForHigherTrust } from "./source-trust.js"; import { hasConversationContinuationPolicy } from "./conversation-continuation.js"; +import { queuedCommentIdsFromWakePayload } from "./issue-queued-comment-queue.js"; const object = (v: unknown): Record => v && typeof v === "object" && !Array.isArray(v) @@ -268,18 +270,39 @@ export async function buildExecutionContinuation(input: { eq(agentWakeupRequests.reason, "retry_failed_run"), eq(agentWakeupRequests.requestedByActorType, "user"), sql`${agentWakeupRequests.payload}->>'issueId' = ${issueId}`, )) : []; - const authorization = reconciliations.map(row => object(row.evidence.explicitUserContinuation)) - .find(value => value.previousRunId === explicitUserSource && + // Admission records the board operator's authority separately from the + // message author. At dispatch, prove that exact queue was adopted by this + // run; caller-supplied continuation context cannot grant this authority. + const continuationAuthorizations = reconciliations.map(row => object(row.evidence.explicitUserContinuation)) + .filter(value => value.previousRunId === explicitUserSource && (!input.runId || value.runId === input.runId) && value.commentId === explicitContinuation.commentId && - priorRuns.some(run => run.id === value.runId) && - (failedRunId + priorRuns.some(run => run.id === value.runId)); + const interruptQueueIds = [...new Set(continuationAuthorizations.flatMap(value => { + const parsed = z.string().guid().safeParse(value.queuedCommentInterruptId); + return parsed.success ? [parsed.data] : []; + }))]; + const interruptQueues = interruptQueueIds.length + ? await db.select().from(agentWakeupRequests).where(and( + inArray(agentWakeupRequests.id, interruptQueueIds), + eq(agentWakeupRequests.companyId, companyId), eq(agentWakeupRequests.agentId, input.agentId), + eq(agentWakeupRequests.status, "coalesced"), + sql`${agentWakeupRequests.payload}->>'issueId' = ${issueId}`, + sql`${agentWakeupRequests.payload}->'queuedCommentInterrupt' is not null`, + )) : []; + const authorization = continuationAuthorizations.find(value => failedRunId ? value.failedRunId === failedRunId && retryWakes.some(wake => wake.runId === value.runId && wake.requestedByActorId === value.actorId && priorRuns.some(run => run.id === wake.runId && run.retryOfRunId === failedRunId)) : rows.some(comment => comment.id === value.commentId && - comment.authorType === "user" && comment.authorUserId === value.actorId && - !comment.createdByRunId && !comment.deletedAt))); + comment.authorType === "user" && + (value.queuedCommentInterruptId + ? interruptQueues.some(queue => queue.id === value.queuedCommentInterruptId && + queue.runId === value.runId && + object(object(queue.payload).queuedCommentInterrupt).actorId === value.actorId && + queuedCommentIdsFromWakePayload(queue.payload).includes(comment.id)) + : comment.authorUserId === value.actorId) && + !comment.createdByRunId && !comment.deletedAt)); if (!predecessor || !authorization || explicitUserSource !== sourceRunId) throw new Error("continuation_user_authorization_missing"); } diff --git a/server/src/services/explicit-native-continuation.test.ts b/server/src/services/explicit-native-continuation.test.ts index 6cffa58668..40a8c45d95 100644 --- a/server/src/services/explicit-native-continuation.test.ts +++ b/server/src/services/explicit-native-continuation.test.ts @@ -43,6 +43,143 @@ const support = await getEmbeddedPostgresTestSupport(); actorType: "user", actorId: "board", reason: "issue_commented" }; } type Fixture = Awaited>; + it.each(["pending", "failed", "historical", "shared", "retained"])("an explicit queued interrupt retries only its stopped sandbox, without granting automatic retries (%s)", async scenario => { + const fails = scenario === "failed"; + const protectedLease = scenario === "shared" || scenario === "retained"; + const f = await seed(), other = await seed(); + await db.update(agents).set({ adapterType: "claude_local" }).where(eq(agents.id, f.agentId)); + await db.delete(nativeRunFinalizations).where(eq(nativeRunFinalizations.runId, f.sourceRunId)); + await db.update(heartbeatRuns).set({ runtimeMode: "legacy", nativeIssueId: null, processPid: null }) + .where(eq(heartbeatRuns.id, f.sourceRunId)); + await db.update(issueRecoveryActions).set({ cause: "legacy_execution_requires_reconciliation" }) + .where(eq(issueRecoveryActions.sourceIssueId, f.issueId)); + await db.insert(heartbeatRuns).values({ companyId: f.companyId, agentId: f.agentId, status: "running" }); + const queueId = randomUUID(); + await db.insert(agentWakeupRequests).values({ id: queueId, companyId: f.companyId, agentId: f.agentId, + source: "automation", reason: "issue_commented", status: "deferred_issue_execution", + requestedByActorType: "system", payload: { issueId: f.issueId, commentId: f.commentId, + _paperclipWakeContext: { wakeCommentIds: [f.commentId] }, + queuedCommentInterrupt: { actorId: "board", requestedAt: new Date().toISOString() } }, + }); + const identities = [f, other].map(fixture => ({ id: randomUUID(), companyId: fixture.companyId, + heartbeatRunId: fixture.sourceRunId, provider: "daytona", providerLeaseId: fixture.sourceRunId })); + for (const identity of identities) await db.insert(environmentLeases).values({ ...identity, + status: "pending_cleanup", leasePolicy: "ephemeral", releasedAt: new Date(), cleanupStatus: "failed", + metadata: { pendingCleanupRetryAttempts: 5, pendingCleanupRetryCapWarned: true } }); + if (scenario === "historical" || protectedLease) await db.update(environmentLeases).set({ + status: "failed", cleanupStatus: "success", + }).where(eq(environmentLeases.id, identities[0].id)); + if (scenario === "shared") await db.update(environmentLeases).set({ + providerLeaseId: identities[0].providerLeaseId, status: "active", releasedAt: null, + }).where(eq(environmentLeases.id, identities[1].id)); + if (scenario === "retained") await db.update(environmentLeases).set({ + status: "retained", leasePolicy: "retain_on_failure", + }).where(eq(environmentLeases.id, identities[0].id)); + const attempted: string[] = []; + const heartbeat = heartbeatService(db, { environmentRuntime: { + isPendingCleanupWorkerReady: async () => true, + retryPendingSandboxTeardown: async ({ lease }: { lease: { id: string; providerLeaseId: string } }) => { + attempted.push(lease.id); + if (fails) throw new Error("Provider unavailable"); + return { providerLeaseId: lease.providerLeaseId, state: "destroyed" }; + }, + } as unknown as HeartbeatEnvironmentRuntime }); + try { + await heartbeat.resumeQueuedCommentInterrupt(f.companyId, queueId); + expect(attempted).toEqual([]); + await heartbeat.resumeQueuedCommentInterrupt(f.companyId, queueId, { retryCleanup: true }); + expect(attempted).toEqual(protectedLease ? [] : [identities[0].id]); + await heartbeat.resumeQueuedCommentInterrupt(f.companyId, queueId); + expect(attempted).toHaveLength(protectedLease ? 0 : 1); + const [queue] = await db.select().from(agentWakeupRequests).where(eq(agentWakeupRequests.id, queueId)); + expect(queue.status).toBe(fails || protectedLease ? "deferred_issue_execution" : "coalesced"); + expect(Boolean(queue.runId)).toBe(!fails && !protectedLease); + const [untouched] = await db.select().from(environmentLeases).where(eq(environmentLeases.id, identities[1].id)); + expect(untouched).toMatchObject({ status: scenario === "shared" ? "active" : "pending_cleanup", metadata: { pendingCleanupRetryAttempts: 5 } }); + } finally { + for (const identity of identities) await db.delete(environmentLeases).where(eq(environmentLeases.id, identity.id)); + } + }); + it("a durable queue interrupt authorizes older legacy messages but still requires the provider to stop", async () => { + const f = await seed(); + await db.update(agents).set({ adapterType: "claude_local" }).where(eq(agents.id, f.agentId)); + await db.delete(nativeRunFinalizations).where(eq(nativeRunFinalizations.runId, f.sourceRunId)); + await db.update(heartbeatRuns).set({ runtimeMode: "legacy", nativeIssueId: null, + processPid: process.pid, + }).where(eq(heartbeatRuns.id, f.sourceRunId)); + await db.update(issueRecoveryActions).set({ cause: "legacy_execution_requires_reconciliation" }) + .where(eq(issueRecoveryActions.sourceIssueId, f.issueId)); + await db.update(issueComments).set({ authorUserId: "original-author", createdAt: new Date("2026-09-11T09:00:00Z") }) + .where(eq(issueComments.id, f.commentId)); + const queueId = randomUUID(); + await db.insert(agentWakeupRequests).values({ id: queueId, companyId: f.companyId, agentId: f.agentId, + source: "on_demand", reason: "issue_commented", status: "deferred_issue_execution", + requestedByActorType: "user", requestedByActorId: "original-author", + payload: { issueId: f.issueId, _paperclipWakeContext: { wakeCommentIds: [f.commentId] }, + queuedCommentInterrupt: { actorId: "board", requestedAt: new Date().toISOString() } }, + }); + const attempt = (queue = queueId) => db.transaction(async tx => { + await tx.select().from(issues).where(eq(issues.id, f.issueId)).for("update"); + return admitExplicitNativeContinuation({ ...f, db: tx as unknown as typeof db, + queuedCommentInterruptId: queue, dryRun: true }); + }); + expect(await attempt()).toBeNull(); + await db.update(heartbeatRuns).set({ processPid: 999999999 }).where(eq(heartbeatRuns.id, f.sourceRunId)); + expect(await attempt(randomUUID())).toBeNull(); + expect(await attempt()).toMatchObject({ previousRunId: f.sourceRunId, commentId: f.commentId }); + await db.update(agentWakeupRequests).set({ status: "cancelled" }).where(eq(agentWakeupRequests.id, queueId)); + expect(await attempt()).toBeNull(); + }); + it("dispatches another user's queued legacy message using the consumed board interrupt receipt", async () => { + const f = await seed(); + await db.update(agents).set({ adapterType: "claude_local" }).where(eq(agents.id, f.agentId)); + await db.delete(nativeRunFinalizations).where(eq(nativeRunFinalizations.runId, f.sourceRunId)); + await db.update(heartbeatRuns).set({ runtimeMode: "legacy", nativeIssueId: null }) + .where(eq(heartbeatRuns.id, f.sourceRunId)); + await db.update(issueRecoveryActions).set({ cause: "legacy_execution_requires_reconciliation" }) + .where(eq(issueRecoveryActions.sourceIssueId, f.issueId)); + await db.update(issueComments).set({ authorUserId: "original-author", createdAt: new Date("2026-09-11T09:00:00Z") }) + .where(eq(issueComments.id, f.commentId)); + // Hold adapter startup so the test can exercise the real dispatch envelope + // deterministically, without invoking a provider. + await db.insert(heartbeatRuns).values({ companyId: f.companyId, agentId: f.agentId, status: "running" }); + const queueId = randomUUID(); + await db.insert(agentWakeupRequests).values({ id: queueId, companyId: f.companyId, agentId: f.agentId, + source: "automation", reason: "issue_commented", status: "deferred_issue_execution", + requestedByActorType: "system", payload: { issueId: f.issueId, commentId: f.commentId, + _paperclipWakeContext: { wakeCommentIds: [f.commentId] }, + queuedCommentInterrupt: { actorId: "board", requestedAt: new Date().toISOString() } }, + }); + await heartbeatService(db).resumeQueuedCommentInterrupt(f.companyId, queueId); + const [receipt] = await db.select().from(agentWakeupRequests).where(eq(agentWakeupRequests.id, queueId)); + expect(receipt.status).toBe("coalesced"); + const [run] = await db.select().from(heartbeatRuns).where(eq(heartbeatRuns.id, receipt.runId!)); + const dispatch = (runId = run.id) => buildExecutionContinuation({ db, companyId: f.companyId, + issueId: f.issueId, agentId: f.agentId, runId, context: run.contextSnapshot!, + summary: null, exposeLowTrustRaw: false }); + const envelope = await dispatch(); + expect(envelope.interruptedRunId).toBe(f.sourceRunId); + expect(envelope.originCommentIds).toContain(f.commentId); + expect(envelope.messages).toEqual(expect.arrayContaining([expect.objectContaining({ id: f.commentId, body: "What happened?" })])); + await expect(dispatch(randomUUID())).rejects.toThrow("continuation_user_authorization_missing"); + for (const patch of [ + { status: "cancelled" }, { runId: f.sourceRunId }, + { payload: { ...receipt.payload, issueId: randomUUID() } }, + { payload: { ...receipt.payload, queuedCommentInterrupt: { actorId: "someone-else" } } }, + { payload: { ...receipt.payload, _paperclipWakeContext: { wakeCommentIds: [] }, commentId: undefined } }, + ]) { + await db.update(agentWakeupRequests).set(patch).where(eq(agentWakeupRequests.id, queueId)); + await expect(dispatch()).rejects.toThrow("continuation_user_authorization_missing"); + await db.update(agentWakeupRequests).set({ status: receipt.status, runId: receipt.runId, payload: receipt.payload }) + .where(eq(agentWakeupRequests.id, queueId)); + } + const [action] = await db.select().from(issueRecoveryActions).where(eq(issueRecoveryActions.sourceIssueId, f.issueId)); + await db.update(issueRecoveryActions).set({ evidence: { ...action.evidence, + explicitUserContinuation: { ...(action.evidence.explicitUserContinuation as Record), + queuedCommentInterruptId: "malformed-historical-receipt" }, + } }).where(eq(issueRecoveryActions.id, action.id)); + await expect(dispatch()).rejects.toThrow("continuation_user_authorization_missing"); + }); const admit = (f: Fixture, dryRun = false) => db.transaction(async tx => { await tx.select().from(issues).where(eq(issues.id, f.issueId)).for("update"); const result = await admitExplicitNativeContinuation({ ...f, dryRun, db: tx as unknown as typeof db }); diff --git a/server/src/services/explicit-native-continuation.ts b/server/src/services/explicit-native-continuation.ts index 7788764e8b..3fd9319f87 100644 --- a/server/src/services/explicit-native-continuation.ts +++ b/server/src/services/explicit-native-continuation.ts @@ -5,7 +5,7 @@ import { hasRemoteTerminationReceipt, remoteLeaseCleanupScope } from "./remote-e import { z } from "zod"; import { and, eq, inArray, isNull, ne, or, sql } from "drizzle-orm"; import { - agents, approvals, issueApprovals, issueThreadInteractions, + agents, agentWakeupRequests, approvals, issueApprovals, issueThreadInteractions, environmentLeases, heartbeatRuns, issueComments, issueRecoveryActions, issues, nativeRunFinalizations, type Db, } from "@paperclipai/db"; @@ -15,6 +15,7 @@ import { adapterExecutionControls } from "./adapter-execution-control.js"; import { persistActivity } from "./activity-log.js"; import { historicalAdapterType, isConversationAdapter } from "./conversation-continuation.js"; +import { queuedCommentIdsFromWakePayload } from "./issue-queued-comment-queue.js"; type Run = typeof heartbeatRuns.$inferSelect; const terminal = ["failed", "interrupted", "timed_out", "cancelled"]; @@ -33,6 +34,8 @@ export async function admitExplicitNativeContinuation(input: { actorType: string | null | undefined; actorId: string | null | undefined; reason: string | null; commentId: string | null; successorRunId: string; failedRunId?: string | null; + /** Server-recorded board intent to send an existing legacy message queue. */ + queuedCommentInterruptId?: string; dryRun?: boolean; resumingSavedMessage?: boolean; onBlocked?: (reason: string, message: string) => void; @@ -48,16 +51,27 @@ export async function admitExplicitNativeContinuation(input: { eq(issues.companyId, companyId), eq(issues.id, issueId), )); if (!task || task.assigneeAgentId !== agentId || ["done", "cancelled"].includes(task.status)) return null; + const [interruptQueue] = input.queuedCommentInterruptId ? await db.select().from(agentWakeupRequests).where(and( + eq(agentWakeupRequests.id, input.queuedCommentInterruptId), + eq(agentWakeupRequests.companyId, companyId), eq(agentWakeupRequests.agentId, agentId), + eq(agentWakeupRequests.status, "deferred_issue_execution"), + sql`${agentWakeupRequests.payload}->>'issueId' = ${issueId}`, + sql`${agentWakeupRequests.payload}->'queuedCommentInterrupt'->>'actorId' = ${actorId}`, + )) : []; + const queuedInterrupt = Boolean(interruptQueue && commentId && + queuedCommentIdsFromWakePayload(interruptQueue.payload).includes(commentId)); + if (input.queuedCommentInterruptId && !queuedInterrupt) return null; const [comment] = retry ? [] : await db.select().from(issueComments).where(and( eq(issueComments.companyId, companyId), eq(issueComments.issueId, issueId), eq(issueComments.id, commentId!), eq(issueComments.authorType, "user"), - eq(issueComments.authorUserId, actorId), isNull(issueComments.createdByRunId), + queuedInterrupt ? undefined : eq(issueComments.authorUserId, actorId), isNull(issueComments.createdByRunId), isNull(issueComments.deletedAt), )); if (!retry && !comment?.body.trim()) return null; const authorizedAt = comment?.createdAt ?? new Date(); const [agent] = await db.select().from(agents).where(and(eq(agents.companyId, companyId), eq(agents.id, agentId))); if (!agent || (!isConversationAdapter(agent.adapterType) && agent.adapterType !== "paperclip_runner")) return null; + if (queuedInterrupt && !isConversationAdapter(agent.adapterType)) return null; const actions = await db.select().from(issueRecoveryActions).where(and( eq(issueRecoveryActions.companyId, companyId), eq(issueRecoveryActions.sourceIssueId, issueId), executionBlockerPredicate(), @@ -90,7 +104,7 @@ export async function admitExplicitNativeContinuation(input: { if (!run || run.agentId !== agentId || !terminal.includes(run.status) || (run.nativeIssueId ?? run.contextSnapshot?.issueId) !== issueId || !run.finishedAt) return blocked("source_unavailable", "The previous execution has not finished or its owner changed. Your message is saved."); - if (authorizedAt <= run.finishedAt) return blocked("message_predates_stop", "This message arrived before the previous run stopped. Send a new message to continue."); + if (!queuedInterrupt && authorizedAt <= run.finishedAt) return blocked("message_predates_stop", "This message arrived before the previous run stopped. Send a new message to continue."); if (adapterExecutionControls.has(run.id)) return blocked("execution_settling", "Waiting for the previous run to stop. Your message will start automatically."); const unusedAdmission = run.status === "cancelled" && !run.startedAt && run.errorCode === "execution_reconciliation_required" && @@ -98,6 +112,7 @@ export async function admitExplicitNativeContinuation(input: { const legacyUserTurn = run.runtimeMode === "legacy" && action.cause === "legacy_execution_requires_reconciliation" && isConversationAdapter(agent.adapterType); + if (queuedInterrupt && !legacyUserTurn) return null; if (legacyUserTurn) { const historicalAdapter = await historicalAdapterType(db, run); // A settings change never converts a known process/webhook execution into @@ -165,7 +180,8 @@ export async function admitExplicitNativeContinuation(input: { context: { previousRunId: previous.id, wakeCommentId: commentId }, summary: null, exposeLowTrustRaw: false }); if (input.dryRun) return { previousRunId: previous.id, commentId, ...(retry ? { failedRunId: input.failedRunId! } : {}) }; - const authorization = { actorId, commentId, ...(retry ? { failedRunId: input.failedRunId } : {}), runId: input.successorRunId, + const authorization = { actorId, commentId, ...(retry ? { failedRunId: input.failedRunId } : {}), + ...(queuedInterrupt ? { queuedCommentInterruptId: input.queuedCommentInterruptId } : {}), runId: input.successorRunId, previousRunId: previous.id, recordedAt: new Date().toISOString() }; for (const runId of cancelledStartupIds) { await db.update(nativeRunFinalizations).set({ diff --git a/server/src/services/heartbeat.ts b/server/src/services/heartbeat.ts index 4c67ac4f40..89a85f6ec8 100644 --- a/server/src/services/heartbeat.ts +++ b/server/src/services/heartbeat.ts @@ -2,9 +2,12 @@ import { AGENT_CHAT_DIRECTIVE, conversationReplay, isConversation, isConversatio import { PROCESS_IDENTITY_RECORDED, recordNativeLocalProcessStop } from "./native-local-process-stop.js"; import { legacyControllerBootId, legacyControllerClaim, renewLegacyControllerLease, hasLiveLegacyController, revokeExpiredLegacyController, watchLegacyControllerLease } from "./legacy-controller-lease.js"; import { completeTerminatedRemoteNativeSessionCleanup } from "../vendor/paperclip-runner/index.js"; -import { remoteExecutionHasStopped, remoteTerminationReceipt, stoppedRemoteCleanupScopes } from "./remote-execution-termination.js"; +import { hasRemoteTerminationReceipt, remoteExecutionHasStopped, remoteTerminationReceipt, stoppedRemoteCleanupScopes } from "./remote-execution-termination.js"; import { applyConnectorSkills, prepareConnectorSkillDelivery, resolveConnectorAssignments } from "./connector-runtime.js"; import { admitExplicitNativeContinuation } from "./explicit-native-continuation.js"; +import { connectionIntentService } from "./connection-intents.js"; +import { prepareManagedAiRuntime, assertManagedAiProjectAuth, stripAiAuthBindings, AI_AUTH_ENV_KEYS } from "./ai-connection-runtime.js"; +import { aiConnectionBindingSchema } from "@paperclipai/shared"; import { executionBlockerPredicate, getExecutionBlocker } from "./execution-blocker.js"; import { CONVERSATION_CONTINUATION_POLICY, claimedAdapterType, runUsedConversationAdapter, hasConversationContinuationPolicy, isConversationAdapter } from "./conversation-continuation.js"; import { recordExecutionWait } from "./execution-wait.js"; @@ -24,7 +27,7 @@ import { buildHeartbeatRunStatusLiveEventPayload } from "./heartbeat-run-status- export { buildHeartbeatRunStatusLiveEventPayload } from "./heartbeat-run-status-payload.js"; import { buildExecutionContinuation } from "./execution-continuation.js"; import { renderPaperclipWakePrompt } from "@paperclipai/adapter-utils/server-utils"; -import { initializeRunIdentity } from "./run-identity.js"; +import { initializeRunIdentity, explicitOperatorRunIdentity } from "./run-identity.js"; import { assertDurableChatWakeupReceipt, assertDurableChatWakeupRequest, @@ -61,6 +64,7 @@ import { gte, inArray, isNull, + isNotNull, lt, lte, ne, @@ -1483,6 +1487,7 @@ function assertLowTrustEnvConfigAllowed(envValue: unknown, source: string) { } export async function resolveExecutionRunAdapterConfig(input: { + managedAiCredentials?: boolean; companyId: string; agentId?: string | null; adapterType?: string | null; @@ -1832,7 +1837,7 @@ export async function resolveExecutionRunAdapterConfig(input: { // host-side login never exists at all. The adapter's execute-time gate // remains the authority there; it probes the sandbox before failing. if ( - (input.adapterType ?? null) === "codex_local" && + !input.managedAiCredentials && (input.adapterType ?? null) === "codex_local" && (input.environmentDriver ?? null) !== "sandbox" ) { const resolvedEnv = parseObject(resolvedConfig.env); @@ -3500,6 +3505,10 @@ function normalizeMaxConcurrentRuns(value: unknown) { } interface WakeupOptions { + /** Set only by authenticated board wake routes; never copied from caller payloads. */ + manualUserWake?: boolean; + /** Internal resume of a queue with persisted board interruption intent. */ + queuedCommentInterruptId?: string; /** Exact failed run selected by an authenticated board Retry request. */ failedRunId?: string | null; durableChatRequest?: DurableChatWakeupRequest; @@ -5653,6 +5662,7 @@ type EffectiveRunWorkspaceConfigCategory = (typeof EFFECTIVE_RUN_WORKSPACE_CONFIG_CATEGORIES)[number]; type EffectiveRunSessionConfigMetadata = { + aiCredentialIdentity?: string; version: typeof EFFECTIVE_RUN_CONFIG_FINGERPRINT_VERSION; fingerprint: string; categories: EffectiveRunSessionConfigCategory[]; @@ -6622,6 +6632,7 @@ function attachPaperclipSessionMetadataToSessionParams( const next = { ...(sessionParams ?? {}) }; if (configuredModel) next[SESSION_CONFIGURED_MODEL_KEY] = configuredModel; if (configMetadata) { + if (configMetadata.aiCredentialIdentity) next.paperclipAiCredentialIdentity = configMetadata.aiCredentialIdentity; next[SESSION_CONFIG_FINGERPRINT_KEY] = configMetadata.fingerprint; next[SESSION_CONFIG_FINGERPRINT_VERSION_KEY] = configMetadata.version; next[SESSION_CONFIG_CATEGORIES_KEY] = configMetadata.categories; @@ -10058,6 +10069,10 @@ export function heartbeatService( for (const wake of pending) { if (wake.idempotencyKey?.startsWith("chat-inbound:")) continue; const payload = parseObject(wake.payload); + if (payload.queuedCommentInterrupt) { + await resumeQueuedCommentInterrupt(wake.companyId, wake.id); + continue; + } const context = parseObject(payload[DEFERRED_WAKE_CONTEXT_KEY]); const commentId = deriveCommentId(context, payload); if (legacyContinuation) { @@ -10097,6 +10112,93 @@ export function heartbeatService( } } + async function resumeQueuedCommentInterrupt(companyId: string, queueId: string, opts?: { retryCleanup?: boolean }) { + const [wake] = await db.select().from(agentWakeupRequests).where(and( + eq(agentWakeupRequests.id, queueId), eq(agentWakeupRequests.companyId, companyId), + eq(agentWakeupRequests.status, "deferred_issue_execution"), + )); + if (!wake) return; + const payload = parseObject(wake.payload); + const actorId = readNonEmptyString(parseObject(payload.queuedCommentInterrupt).actorId); + const commentIds = queuedCommentIdsFromWakePayload(payload); + const issueId = readNonEmptyString(payload.issueId); + if (!actorId || !issueId || !commentIds.length) return; + const agent = await getAgent(wake.agentId); + if (!agent || agent.companyId !== companyId || agent.adapterType === "paperclip_runner") return; + const [active] = await db.select({ id: heartbeatRuns.id }).from(heartbeatRuns).where(and( + eq(heartbeatRuns.companyId, companyId), + sql`${heartbeatRuns.contextSnapshot}->>'issueId' = ${issueId}`, + inArray(heartbeatRuns.status, ["running", "queued", "scheduled_retry"]), + )).limit(1); + if (active) return; + if (opts?.retryCleanup) { + // Only the HTTP click grants an extra cleanup attempt. Periodic retries + // reuse the intent to deliver, never a fresh provider teardown budget. + const sourceRun = await db.transaction(async tx => { + const [task] = await tx.select().from(issues).where(and( + eq(issues.companyId, companyId), eq(issues.id, issueId), + )).for("update"); + const [current] = await tx.select().from(agentWakeupRequests).where(and( + eq(agentWakeupRequests.id, queueId), eq(agentWakeupRequests.companyId, companyId), + eq(agentWakeupRequests.agentId, wake.agentId), eq(agentWakeupRequests.status, "deferred_issue_execution"), + sql`${agentWakeupRequests.payload}->>'issueId' = ${issueId}`, + sql`${agentWakeupRequests.payload}->'queuedCommentInterrupt'->>'actorId' = ${actorId}`, + )); + if (!task || task.assigneeAgentId !== wake.agentId || ["done", "cancelled"].includes(task.status) || + !current || !queuedCommentIdsFromWakePayload(current.payload).length) return null; + const [successor] = await tx.select({ id: heartbeatRuns.id }).from(heartbeatRuns).where(and( + eq(heartbeatRuns.companyId, companyId), + sql`${heartbeatRuns.contextSnapshot}->>'issueId' = ${issueId}`, + inArray(heartbeatRuns.status, ["running", "queued", "scheduled_retry"]), + )).limit(1); + if (successor) return null; + const blocker = await getExecutionBlocker(tx as unknown as Db, companyId, issueId); + const run = blocker?.runId ? await tx.select().from(heartbeatRuns).where(and( + eq(heartbeatRuns.companyId, companyId), eq(heartbeatRuns.id, blocker.runId), + eq(heartbeatRuns.agentId, wake.agentId), eq(heartbeatRuns.runtimeMode, "legacy"), + inArray(heartbeatRuns.status, ["failed", "timed_out", "interrupted", "cancelled"]), + sql`${heartbeatRuns.contextSnapshot}->>'issueId' = ${issueId}`, + )).then(rows => rows[0]) : null; + if (!run || activeRunExecutions.has(run.id) || adapterExecutionControls.has(run.id)) return null; + // Older ephemeral leases recorded successful cleanup without a provider + // receipt. Re-verify them through the recorded teardown path; a timestamp + // alone never certifies termination. Retained/reusable resources stay put. + const historical = await tx.select().from(environmentLeases).where(and( + eq(environmentLeases.companyId, companyId), eq(environmentLeases.heartbeatRunId, run.id), + eq(environmentLeases.leasePolicy, "ephemeral"), isNotNull(environmentLeases.releasedAt), + inArray(environmentLeases.status, ["released", "expired", "failed"]), + )).for("update"); + for (const lease of historical) { + if (!lease.provider || lease.provider === "local" || !lease.providerLeaseId || hasRemoteTerminationReceipt(lease)) continue; + const [otherOwner] = await tx.select({ id: environmentLeases.id }).from(environmentLeases).where(and( + ne(environmentLeases.id, lease.id), eq(environmentLeases.provider, lease.provider), + eq(environmentLeases.providerLeaseId, lease.providerLeaseId), + or(isNull(environmentLeases.releasedAt), inArray(environmentLeases.status, ["active", "retained", "pending_cleanup"])), + )).limit(1); + if (otherOwner) continue; + await tx.update(environmentLeases).set({ status: "pending_cleanup", updatedAt: new Date() }) + .where(eq(environmentLeases.id, lease.id)); + } + return run; + }); + if (sourceRun) await sweepPendingCleanupLeases({ explicitRetry: { + companyId, runId: sourceRun.id, actorId, reason: "queued_comment_interrupt", + } }); + } + const deliveryPayload = { ...payload }; + delete deliveryPayload.queuedCommentInterrupt; + await enqueueWakeup(wake.agentId, { + source: "on_demand", triggerDetail: "manual", reason: "issue_commented", + payload: deliveryPayload, contextSnapshot: withQueuedCommentIdsInRunContext({ + issueId, triggeredBy: "board", actorId, responsibleUserId: actorId, + }, commentIds), + requestedByActorType: "user", requestedByActorId: actorId, + queuedCommentInterruptId: queueId, + issueStateGuard: { assigneeAgentId: wake.agentId, statuses: ["todo", "in_progress", "in_review", "blocked"] }, + idempotencyKey: `queued-comment-interrupt:${queueId}`, + }, queueId); + } + async function resumeExecutionWaitComments() { if ((await getSchedulingSuppression()).suppressed) return; const waits = await db.select({ wake: agentWakeupRequests }) @@ -10108,6 +10210,7 @@ export function heartbeatService( .where(and(eq(agentWakeupRequests.status, "deferred_issue_execution"), eq(agentWakeupRequests.requestedByActorType, "user"), sql`${agentWakeupRequests.payload}->'executionWait' is not null`, + sql`${agentWakeupRequests.payload}->'queuedCommentInterrupt' is null`, lte(agentWakeupRequests.updatedAt, new Date(Date.now() - 30_000)), notInArray(issues.status, ["done", "cancelled"]))) .orderBy(asc(agentWakeupRequests.updatedAt)).limit(50); @@ -10632,7 +10735,8 @@ export function heartbeatService( ReturnType >; }) { - const responsibleUserId = await resolveResponsibleUserIdForRunSeed({ + const operatorIdentity = await explicitOperatorRunIdentity(db, input.run); + const responsibleUserId = operatorIdentity?.actorId ?? await resolveResponsibleUserIdForRunSeed({ companyId: input.run.companyId, contextSnapshot: input.contextSnapshot, issueContext: input.issueContext, @@ -17853,7 +17957,7 @@ export function heartbeatService( * A later user Retry may try again after a provider failure; automatic * sweeps retain their exhausted budget and never gain extra attempts. */ - explicitRetry?: { companyId: string; runId: string; actorId: string }; + explicitRetry?: { companyId: string; runId: string; actorId: string; reason?: "retry_failed_run" | "queued_comment_interrupt" }; }): Promise<{ swept: number; destroyed: number; @@ -17983,7 +18087,7 @@ export function heartbeatService( if (opts?.explicitRetry) await logActivity(db, { companyId: row.companyId, actorType: "user", actorId: opts.explicitRetry.actorId, action: "environment_lease.cleanup_retried", entityType: "environment_lease", entityId: row.id, - runId: opts.explicitRetry.runId, details: { attempt: attempts + 1, reason: "retry_failed_run" }, + runId: opts.explicitRetry.runId, details: { attempt: attempts + 1, reason: opts.explicitRetry.reason ?? "retry_failed_run" }, }); try { @@ -18695,6 +18799,52 @@ export function heartbeatService( if ((await getSchedulingSuppression()).suppressed) return; await resumeExecutionWaitComments(); const cutoff = await getWorktreeExecutionCutoff(); + const pendingInterrupts = await db.select({ id: agentWakeupRequests.id, companyId: agentWakeupRequests.companyId }) + .from(agentWakeupRequests).innerJoin(companies, eq(companies.id, agentWakeupRequests.companyId)) + .where(and(eq(agentWakeupRequests.status, "deferred_issue_execution"), + eq(companies.status, "active"), + sql`${agentWakeupRequests.payload}->'queuedCommentInterrupt' is not null`, + lte(agentWakeupRequests.updatedAt, new Date(Date.now() - 30_000)), + cutoff ? gte(agentWakeupRequests.requestedAt, cutoff) : undefined)) + .orderBy(asc(agentWakeupRequests.updatedAt)).limit(50); + for (const wake of pendingInterrupts) { + await db.update(agentWakeupRequests).set({ updatedAt: new Date() }).where(and( + eq(agentWakeupRequests.id, wake.id), eq(agentWakeupRequests.status, "deferred_issue_execution"), + )); + await resumeQueuedCommentInterrupt(wake.companyId, wake.id).catch(err => { + logger.warn({ err, queueId: wake.id }, "failed to resume interrupted comment queue"); + }); + } + // A server restart or a message/cleanup race can leave a deferred wake + // after its owner has released the issue lock. Revisit it through the same + // release admission, so recovery holds and operator Stops still apply. + const strandedQueues = await db.select({ wake: agentWakeupRequests }) + .from(agentWakeupRequests) + .innerJoin(issues, and(eq(issues.companyId, agentWakeupRequests.companyId), + sql`${issues.id}::text = ${agentWakeupRequests.payload}->>'issueId'`, + eq(issues.assigneeAgentId, agentWakeupRequests.agentId))) + .innerJoin(companies, and(eq(companies.id, issues.companyId), eq(companies.status, "active"))) + .where(and(eq(agentWakeupRequests.status, "deferred_issue_execution"), + isNull(issues.executionRunId), + sql`jsonb_typeof(${agentWakeupRequests.payload} #> '{_paperclipWakeContext,wakeCommentIds}') = 'array'`, + sql`${agentWakeupRequests.payload} #> '{_paperclipWakeContext,wakeCommentIds}' <> '[]'::jsonb`, + sql`${agentWakeupRequests.payload}->'queuedCommentInterrupt' is null`, + cutoff ? gte(agentWakeupRequests.requestedAt, cutoff) : undefined)) + .orderBy(asc(agentWakeupRequests.updatedAt)).limit(50); + for (const { wake } of strandedQueues) { + if (!queuedCommentIdsFromWakePayload(wake.payload).length) continue; + const [latest] = await db.select().from(heartbeatRuns).where(and( + eq(heartbeatRuns.companyId, wake.companyId), eq(heartbeatRuns.agentId, wake.agentId), + sql`${heartbeatRuns.contextSnapshot}->>'issueId' = ${String(wake.payload?.issueId)}`, + )).orderBy(desc(heartbeatRuns.createdAt), desc(heartbeatRuns.id)).limit(1); + await db.update(agentWakeupRequests).set({ updatedAt: new Date() }).where(and( + eq(agentWakeupRequests.id, wake.id), eq(agentWakeupRequests.status, "deferred_issue_execution"), + )); + if (!latest || latest.runtimeMode !== "legacy" || !isHeartbeatRunTerminalStatus(latest.status)) continue; + await releaseIssueExecutionAndPromote(latest, { suppressImmediateRecovery: true }).catch(err => { + logger.warn({ err, queueId: wake.id }, "failed to promote stranded legacy comments"); + }); + } // The cancellation marker is durable intent. Retry while its exact queue // is still deferred, including after a failed cleanup promotion or restart. @@ -19423,6 +19573,7 @@ export function heartbeatService( "keep_running" | "stop_and_reuse" | "destroy_after_turn"; } | undefined; + let managedAiRuntime: Awaited> | undefined; let providerTraceCapture: Awaited< ReturnType > | null = null; @@ -20483,8 +20634,10 @@ export function heartbeatService( ["local", "ssh"].includes( selectedEnvironmentForConfig?.driver ?? "local", ); + const aiBinding = agent.runtimeConfig?.aiConnection ? aiConnectionBindingSchema.parse(agent.runtimeConfig.aiConnection) : undefined; const { resolvedConfig, secretKeys, secretManifest } = await resolveExecutionRunAdapterConfig({ + managedAiCredentials: Boolean(aiBinding), managedGitHubCredentials: !useHostGitHub, companyId: agent.companyId, agentId: agent.id, @@ -20492,17 +20645,40 @@ export function heartbeatService( issueId, heartbeatRunId: run.id, environmentId: selectedEnvironmentForConfig?.id ?? null, - environmentEnv: selectedEnvironmentForConfig?.envVars ?? null, + environmentEnv: aiBinding ? stripAiAuthBindings(selectedEnvironmentForConfig?.envVars) : selectedEnvironmentForConfig?.envVars ?? null, environmentDriver: selectedEnvironmentForConfig?.driver ?? null, projectId: projectContext?.id ?? null, routineId: routineEnvContext.routineId, responsibleUserId, - executionRunConfig, - projectEnv: projectContext?.env ?? null, - routineEnv: routineEnvContext.env, + executionRunConfig: aiBinding ? { ...executionRunConfig, env: stripAiAuthBindings(executionRunConfig.env) } : executionRunConfig, + projectEnv: aiBinding ? stripAiAuthBindings(projectContext?.env) : projectContext?.env ?? null, + routineEnv: aiBinding ? stripAiAuthBindings(routineEnvContext.env) : routineEnvContext.env, secretsSvc, trustPreset, }); + if (aiBinding) { + try { + managedAiRuntime = await prepareManagedAiRuntime(db, { companyId: agent.companyId, agentId: agent.id, responsibleUserId, adapterType: agent.adapterType, binding: aiBinding, config: resolvedConfig }); + } catch (error) { + if (responsibleUserId && issueId && aiBinding.mode === "responsible_user") { + await connectionIntentService(db).request({ sub: agent.id, company_id: agent.companyId, run_id: run.id, responsible_user_id: responsibleUserId }, aiBinding.provider, { purpose: "ai" }).catch(() => { + logger.warn({ runId: run.id, agentId: agent.id }, "Could not attach AI connection request; runtime configuration action remains available"); + }); + } + throw new ConfigurationIncompleteFailure(error instanceof Error ? error.message : "Configure this agent’s AI connection", { + configurationIncomplete: { reason: "ai_connection_unavailable", companyId: agent.companyId, agentId: agent.id, responsibleUserId, + provider: aiBinding.provider, method: aiBinding.method, actionUrl: `/agents/${agent.id}/runtime`, + fingerprint: `ai:${agent.id}:${responsibleUserId}:${JSON.stringify(aiBinding)}` }, + }); + } + if (persistedNativeExecutionInput && parseObject(run.contextSnapshot?.aiConnection).identity !== managedAiRuntime.identity) { + throw new ConfigurationIncompleteFailure("The AI account changed while this native run was suspended. Start a new execution.", { configurationIncomplete: { reason: "ai_connection_changed", actionUrl: `/agents/${agent.id}/runtime` } }); + } + Object.assign(resolvedConfig, managedAiRuntime.config); + for (const key of AI_AUTH_ENV_KEYS) secretKeys.add(key); + context.aiConnection = { ...managedAiRuntime.attribution, identity: managedAiRuntime.identity }; + await db.update(heartbeatRuns).set({ contextSnapshot: sql`coalesce(${heartbeatRuns.contextSnapshot}, '{}'::jsonb) || ${JSON.stringify({ aiConnection: context.aiConnection })}::jsonb` }).where(eq(heartbeatRuns.id, run.id)); + } if (secretManifest.length > 0) { context.paperclipSecrets = { manifest: secretManifest, @@ -21439,6 +21615,10 @@ export function heartbeatService( ].filter(Boolean).join("\n\n"); workFolderSaveFailed = false; } + if (managedAiRuntime && aiBinding) { + try { await assertManagedAiProjectAuth({ ...resolvedConfig, cwd: executionWorkspace.cwd }, aiBinding.provider, executionTarget); } + catch { throw new ConfigurationIncompleteFailure("Project authentication conflicts with this agent’s managed AI connection", { configurationIncomplete: { reason: "ai_connection_incompatible", actionUrl: `/agents/${agent.id}/runtime` } }); } + } const remoteExecution = realizationResult.remoteExecution; if ( nativeChatWorkspaceScope && @@ -21808,6 +21988,15 @@ export function heartbeatService( delete context.paperclipPreviousSessionId; } + if (managedAiRuntime) { + sessionConfigMetadata.aiCredentialIdentity = managedAiRuntime.identity; + if (taskSessionDecodedParams?.paperclipAiCredentialIdentity !== managedAiRuntime.identity) { + runtimeSessionIdForAdapter = null; + runtimeSessionParamsForAdapter = null; + previousSessionDisplayId = null; + delete executionContinuation?.resumeDelta; + } + } const runtimeForAdapter = { sessionId: runtimeSessionIdForAdapter, sessionParams: runtimeSessionParamsForAdapter, @@ -22311,7 +22500,9 @@ export function heartbeatService( return requests.length > 0 ? requests : undefined; })(), }); - const taskNativeSessionId = sandboxWorkFolders?.identityChanged ? null : readNonEmptyString( + const taskSessionIdentityChanged = Boolean(sandboxWorkFolders?.identityChanged + || (managedAiRuntime && taskSessionDecodedParams?.paperclipAiCredentialIdentity !== managedAiRuntime.identity)); + const taskNativeSessionId = taskSessionIdentityChanged ? null : readNonEmptyString( taskSessionDecodedParams?.sessionId, ); // Compatibility for native retry rows created before same-run restart @@ -22319,7 +22510,7 @@ export function heartbeatService( // inherit its source checkpoint; any process/provider evidence on the // replacement makes the ownership ambiguous and therefore ineligible. const legacyRetrySource = - !sandboxWorkFolders?.identityChanged && run.retryOfRunId && !isFailedChatRunRetry + !taskSessionIdentityChanged && run.retryOfRunId && !isFailedChatRunRetry ? await db .select({ id: heartbeatRuns.id, @@ -22367,7 +22558,7 @@ export function heartbeatService( .then((rows) => rows.length > 0) : false; const compatibleLegacyRetrySource = - !isConversation(issueContext) && context.forceFreshSession !== true && isUnusedLegacyNativeRetryReplacement({ + !managedAiRuntime && !isConversation(issueContext) && context.forceFreshSession !== true && isUnusedLegacyNativeRetryReplacement({ replacement: run, source: legacyRetrySource, hasProviderEvents: nativeBootstrapHasProviderEvidence, @@ -22431,8 +22622,7 @@ export function heartbeatService( executionTarget.transport === "sandbox" ? (executionTarget.runnerLifecyclePolicy ?? null) : null; - const effectiveLifecyclePolicy = - environmentLifecyclePolicy ?? agentLifecyclePolicy; + const effectiveLifecyclePolicy = managedAiRuntime ? { mode: "per_turn" as const, idleTimeoutMs: null } : environmentLifecyclePolicy ?? agentLifecyclePolicy; if ( effectiveLifecyclePolicy.mode === "warm" && executionTarget?.kind === "remote" && @@ -23334,6 +23524,7 @@ export function heartbeatService( // Bootstrap with executable/home discovery while keeping // configured provider values and the server-selected // workspace boundary authoritative. + managedAiCredentialHome: managedAiRuntime ? String((managedAiRuntime.config.env as Record).CODEX_HOME) : undefined, runnerEnvironment: { ...buildNativeProviderEnvironment( adapterEnv, @@ -25043,6 +25234,7 @@ export function heartbeatService( try { await sandboxWorkFolders.stop(beforeWorkFolderCompletion); workFolderSaveFailed = false; } catch (error) { workFolderSaveFailed = true; logger.error({ err: error, runId: run.id }, "Work folder save failed; retaining sandbox for recovery"); } } + if (managedAiRuntime) await managedAiRuntime.cleanup().catch(() => logger.warn({ runId: run.id }, "AI connection refresh or cleanup failed")); let latestRun = await getRun(run.id).catch(() => null); try { if (latestRun && isHeartbeatRunTerminalStatus(latestRun.status)) { @@ -25213,18 +25405,6 @@ export function heartbeatService( ${JSON.stringify({ startupPreparationSettledAt: new Date().toISOString() })}::jsonb`, }).where(and(eq(heartbeatRuns.id, run.id), eq(heartbeatRuns.status, "cancelled"))); } - // Interrupting a queued message explicitly authorizes the pending queue. - // Retry its normal promotion after leases and adapter cleanup have settled; - // the earlier terminal write can still have an execution blocker here. - if ( - latestRun?.status === "cancelled" && - latestRun.runtimeMode !== "native" && - readNonEmptyString(latestRun.resultJson?.queuedCommentInterruptQueueId) - ) { - await releaseIssueExecutionAndPromote(latestRun, { suppressImmediateRecovery: true }).catch((err) => { - logger.error({ err, runId: run.id }, "failed to promote interrupted comment queue after cleanup"); - }); - } } finally { controllerLease.stop(); activeRunExecutions.delete(run.id); @@ -25237,6 +25417,20 @@ export function heartbeatService( adapterExecutionControls.delete(run.id); } } + // Terminalization precedes lease and adapter cleanup. Only now is the + // owner gone; retry pending input for ordinary completions as well as Stop. + if (latestRun?.runtimeMode === "legacy" && isHeartbeatRunTerminalStatus(latestRun.status)) { + const [pending] = await db.select({ id: agentWakeupRequests.id, payload: agentWakeupRequests.payload }).from(agentWakeupRequests).where(and( + eq(agentWakeupRequests.companyId, run.companyId), eq(agentWakeupRequests.agentId, run.agentId), + eq(agentWakeupRequests.status, "deferred_issue_execution"), + sql`${agentWakeupRequests.payload}->>'issueId' = ${String(latestRun.contextSnapshot?.issueId)}`, + )).limit(1); + if (pending) await (pending.payload?.queuedCommentInterrupt + ? resumeQueuedCommentInterrupt(run.companyId, pending.id) + : releaseIssueExecutionAndPromote(latestRun, { suppressImmediateRecovery: true })).catch(err => { + logger.error({ err, runId: run.id }, "failed to promote legacy comment queue after cleanup"); + }); + } if ( !nativeSessionResumeScheduled && !nativeWorkspaceFinalizeScheduled && @@ -25282,7 +25476,19 @@ export function heartbeatService( ...(opts.contextSnapshot ?? {}), }; const reason = opts.reason ?? null; - const payload = opts.payload ?? null; + let payload = opts.payload ? { ...opts.payload } : null; + // Only the board queue route can record interruption authority on an + // existing receipt. Never accept this internal marker from a wake caller. + if (payload) { + delete payload.queuedCommentInterrupt; + delete payload.manualUserWake; + } + if (opts.manualUserWake) { + if (opts.requestedByActorType !== "user" || !opts.requestedByActorId || opts.failedRunId) { + throw new HttpError(403, "Manual wake requires an authenticated user"); + } + payload = { ...payload, manualUserWake: true }; + } const executionReconciliationWake = contextSnapshot.source === "execution.reconciled" || opts.idempotencyKey?.startsWith("execution-reconciliation:") === true; @@ -25307,6 +25513,9 @@ export function heartbeatService( if (issueId) { const conversation = await getIssueExecutionContext(agent.companyId, issueId); if (isConversation(conversation)) { + if (opts.manualUserWake && conversation!.conversationUserId !== opts.requestedByActorId) { + throw new HttpError(403, "Only the conversation owner can start a chat run"); + } if (isConversationExecutionWake(conversation, reason ?? readNonEmptyString(enrichedContextSnapshot.wakeReason))) return null; if (agent.id !== conversation!.conversationAgentId) return null; if (!(await instanceSettings.getExperimental()).enableAgentChat) return null; @@ -25628,8 +25837,10 @@ export function heartbeatService( const isolatedWorkspacesEnabled = issueId ? (await instanceSettings.getExperimental()).enableIsolatedWorkspaces : false; + let operatorResponsibleUserId: string | null = opts.manualUserWake ? opts.requestedByActorId! : null; let queuedResponsibleUserIdPromise: Promise | null = null; const resolveQueuedResponsibleUserId = () => { + if (operatorResponsibleUserId) return Promise.resolve(operatorResponsibleUserId); queuedResponsibleUserIdPromise ??= (async () => { const queuedIssueContext = issueId ? await getIssueExecutionContext(agent.companyId, issueId) @@ -25809,8 +26020,13 @@ export function heartbeatService( const [pending] = await tx.select().from(agentWakeupRequests).where(and( eq(agentWakeupRequests.id, executionWaitRequestId), eq(agentWakeupRequests.companyId, agent.companyId), eq(agentWakeupRequests.agentId, agentId), eq(agentWakeupRequests.status, "deferred_issue_execution"), - eq(agentWakeupRequests.requestedByActorType, "user"), - eq(agentWakeupRequests.requestedByActorId, opts.requestedByActorId ?? ""), + // A user message can join a queue originally created by a + // system wake. The recorded board click supplies fresh authority. + opts.queuedCommentInterruptId === executionWaitRequestId + ? undefined : eq(agentWakeupRequests.requestedByActorType, "user"), + opts.queuedCommentInterruptId === executionWaitRequestId + ? sql`${agentWakeupRequests.payload}->'queuedCommentInterrupt'->>'actorId' = ${opts.requestedByActorId ?? ""}` + : eq(agentWakeupRequests.requestedByActorId, opts.requestedByActorId ?? ""), sql`${agentWakeupRequests.payload}->>'issueId' = ${issueId}`, )); // The issue lock serializes cleanup callbacks and periodic workers. @@ -25821,11 +26037,27 @@ export function heartbeatService( const [comment] = await tx.select({ id: issueComments.id }).from(issueComments).where(and( eq(issueComments.companyId, agent.companyId), eq(issueComments.issueId, issueId), sql`${issueComments.id}::text = ${wakeCommentId}`, eq(issueComments.authorType, "user"), - eq(issueComments.authorUserId, opts.requestedByActorId ?? ""), + opts.queuedCommentInterruptId === executionWaitRequestId + ? undefined : eq(issueComments.authorUserId, opts.requestedByActorId ?? ""), isNull(issueComments.deletedAt), isNull(issueComments.createdByRunId), sql`length(trim(${issueComments.body})) > 0`, )); if (!comment) return { kind: "deferred" as const }; + if (!opts.queuedCommentInterruptId && pending.payload?.manualUserWake === true) { + // A persisted manual wake keeps its actor when an execution wait + // resumes. The locked receipt above has revalidated that actor. + payload = { ...payload, manualUserWake: true }; + operatorResponsibleUserId = opts.requestedByActorId!; + } + if (opts.queuedCommentInterruptId) { + // The locked board receipt supplies execution authority even when + // another user authored the messages. Dispatch revalidates the receipt. + operatorResponsibleUserId = opts.requestedByActorId!; + // Edits/discards between the click and dispatch remain authoritative. + Object.assign(enrichedContextSnapshot, withQueuedCommentIdsInRunContext( + enrichedContextSnapshot, queuedCommentIdsFromWakePayload(pending.payload), + )); + } } let automaticParentRunId: string | null = null; if ( @@ -26205,6 +26437,7 @@ export function heartbeatService( db: tx as unknown as Db, companyId: issue.companyId, issueId: issue.id, agentId, actorType: opts.requestedByActorType, actorId: opts.requestedByActorId, reason, commentId: wakeCommentId ?? null, failedRunId: opts.failedRunId, successorRunId: explicitContinuationRunId, + queuedCommentInterruptId: opts.queuedCommentInterruptId, dryRun: true, onBlocked: (reason, message) => { continuationWait = { reason, message }; }, }))) return deferBlockedExecution(executionBlocker); @@ -26973,6 +27206,7 @@ export function heartbeatService( continuationRejected = true; continuationWait = { reason, message }; }, + queuedCommentInterruptId: opts.queuedCommentInterruptId, }); // Recovery can change while earlier admission gates await I/O. Use // the current blocker, not the snapshot from the start of admission. @@ -27023,6 +27257,7 @@ export function heartbeatService( .orderBy(asc(agentWakeupRequests.requestedAt)) : []; const adoptedComments = pendingComments.filter((wake) => { + if (wake.id === opts.queuedCommentInterruptId) return true; const deferredPayload = parseObject(wake.payload); const deferredContext = parseObject( deferredPayload[DEFERRED_WAKE_CONTEXT_KEY], @@ -27210,8 +27445,9 @@ export function heartbeatService( contextSnapshot: enrichedContextSnapshot, wakeCommentId, }); + // Unscoped manual wakes need their own receipt and execution identity too. const rawCoalescedTarget = - opts.allowRunCoalescing === false + opts.allowRunCoalescing === false || opts.manualUserWake ? null : (sameScopeQueuedRun ?? sameScopeScheduledRetryRun ?? @@ -28615,6 +28851,7 @@ export function heartbeatService( releaseEnvironmentLeasesForRun, resumeRemoteStopComments, + resumeQueuedCommentInterrupt, resumeExecutionWaitComments, sweepStaleIssueLocks, diff --git a/server/src/services/issue-thread-interactions.ts b/server/src/services/issue-thread-interactions.ts index 3d1dc49123..9b973d962b 100644 --- a/server/src/services/issue-thread-interactions.ts +++ b/server/src/services/issue-thread-interactions.ts @@ -2537,7 +2537,7 @@ export function issueThreadInteractionService( || existing.sourceRunId !== input.sourceRunId || existing.addresseeUserId !== input.addresseeUserId || (existing.kind === "connection_intent" - ? connectionIntentPayloadSchema.parse(existing.payload).serviceSlug !== payload.serviceSlug + ? (connectionIntentPayloadSchema.parse(existing.payload).serviceSlug !== payload.serviceSlug || connectionIntentPayloadSchema.parse(existing.payload).purpose !== payload.purpose) : !isDeepStrictEqual(existing.payload, payload)) ) { throw conflict( @@ -2574,7 +2574,7 @@ export function issueThreadInteractionService( eq(issueThreadInteractions.addresseeUserId, input.addresseeUserId), )); const reusable = pending.find((candidate) => - connectionIntentPayloadSchema.parse(candidate.payload).serviceSlug === payload.serviceSlug); + connectionIntentPayloadSchema.parse(candidate.payload).serviceSlug === payload.serviceSlug && connectionIntentPayloadSchema.parse(candidate.payload).purpose === payload.purpose); if (reusable) return reusable; const [sourceRun] = await tx.select({ context: heartbeatRuns.contextSnapshot }).from(heartbeatRuns) @@ -2628,7 +2628,7 @@ export function issueThreadInteractionService( ); return ( candidatePayload.success && - candidatePayload.data.serviceSlug === payload.serviceSlug + candidatePayload.data.serviceSlug === payload.serviceSlug && candidatePayload.data.purpose === payload.purpose ); }) .map((candidate) => candidate.id); diff --git a/server/src/services/issues.ts b/server/src/services/issues.ts index d15fb00a59..c74483e250 100644 --- a/server/src/services/issues.ts +++ b/server/src/services/issues.ts @@ -1,4 +1,5 @@ import { documentService } from "./documents.js"; +import { parseTaskSearch, taskSearchCtes, taskSearchScore } from "./task-search.js"; import { createdFromIssueCondition } from "./issue-creation-origin.js"; import { executionProjectionsForRuns } from "./execution-projection.js"; import type { ExecutionProjection } from "@paperclipai/shared"; @@ -7810,24 +7811,7 @@ export function issueService(db: Db) { filters?.includeLiveDescendantSummary === true; const rawSearch = filters?.q?.trim() ?? ""; const hasSearch = rawSearch.length > 0; - const escapedSearch = hasSearch ? escapeLikePattern(rawSearch) : ""; - const startsWithPattern = `${escapedSearch}%`; - const containsPattern = `%${escapedSearch}%`; - const titleStartsWithMatch = sql`${issues.title} ILIKE ${startsWithPattern} ESCAPE '\\'`; - const titleContainsMatch = sql`${issues.title} ILIKE ${containsPattern} ESCAPE '\\'`; - const identifierStartsWithMatch = sql`${issues.identifier} ILIKE ${startsWithPattern} ESCAPE '\\'`; - const identifierContainsMatch = sql`${issues.identifier} ILIKE ${containsPattern} ESCAPE '\\'`; - const descriptionContainsMatch = sql`${issues.description} ILIKE ${containsPattern} ESCAPE '\\'`; - const commentContainsMatch = sql` - EXISTS ( - SELECT 1 - FROM ${issueComments} - WHERE ${issueComments.issueId} = ${issues.id} - AND ${issueComments.companyId} = ${companyId} - AND ${issueComments.deletedAt} IS NULL - AND ${issueComments.body} ILIKE ${containsPattern} ESCAPE '\\' - ) - `; + const taskSearch = parseTaskSearch(rawSearch); if (filters?.createdFromIssueId) { conditions.push(createdFromIssueCondition(companyId, filters.createdFromIssueId)); } @@ -7935,16 +7919,6 @@ export function issueService(db: Db) { ), ); } - if (hasSearch) { - conditions.push( - or( - titleContainsMatch, - identifierContainsMatch, - descriptionContainsMatch, - commentContainsMatch, - )!, - ); - } if (filters?.updatedSince) { const since = new Date(filters.updatedSince); if (Number.isFinite(since.getTime())) { @@ -7959,20 +7933,15 @@ export function issueService(db: Db) { conditions.push(ne(issues.originKind, "routine_execution")); } const priorityOrder = sql`CASE ${issues.priority} WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 ELSE 4 END`; - const searchOrder = sql` - CASE - WHEN ${titleStartsWithMatch} THEN 0 - WHEN ${titleContainsMatch} THEN 1 - WHEN ${identifierStartsWithMatch} THEN 2 - WHEN ${identifierContainsMatch} THEN 3 - WHEN ${commentContainsMatch} THEN 4 - WHEN ${descriptionContainsMatch} THEN 5 - ELSE 6 - END - `; - const baseQuery = db - .select(issueListSelect) - .from(issues) + const searchOrder = sql`-task_search.score`; + const issueSource = db.select(issueListSelect).from(issues); + const searchedSource = hasSearch + ? issueSource.innerJoin(sql`( + ${taskSearchCtes(companyId, taskSearch, true, and(...conditions))} + SELECT m.id, ${taskSearchScore(taskSearch)} AS score FROM matched m + ) task_search`, sql`task_search.id = ${issues.id}`) + : issueSource; + const baseQuery = searchedSource .where(and(...conditions)) .orderBy( ...issueListOrderBy(companyId, { diff --git a/server/src/services/local-ai-credential-file.ts b/server/src/services/local-ai-credential-file.ts new file mode 100644 index 0000000000..4641dde599 --- /dev/null +++ b/server/src/services/local-ai-credential-file.ts @@ -0,0 +1,25 @@ +import { openRunnerApiWorkspaceFile } from "./native-runtime/runner-api-files.js"; + +const MAX_CREDENTIAL_BYTES = 64 * 1024; + +/** Bounded descriptor read; never follows symlinks or reopens a checked path. */ +export async function readLocalAiCredentialFile(filename: string): Promise { + const file = await openRunnerApiWorkspaceFile(filename); + try { + const stat = await file.stat(); + if (!stat.isFile() || stat.uid !== process.getuid?.() || (stat.mode & 0o777) !== 0o600 || stat.size > MAX_CREDENTIAL_BYTES) { + throw new Error("Invalid credential file"); + } + const bytes = Buffer.alloc(MAX_CREDENTIAL_BYTES + 1); + let size = 0; + while (size < bytes.length) { + const read = await file.read(bytes, size, bytes.length - size, size); + if (!read.bytesRead) break; + size += read.bytesRead; + } + if (size > MAX_CREDENTIAL_BYTES) throw new Error("Invalid credential file"); + return bytes.subarray(0, size).toString("utf8"); + } finally { + await file.close(); + } +} diff --git a/server/src/services/local-ai-credentials.ts b/server/src/services/local-ai-credentials.ts new file mode 100644 index 0000000000..ece03e792e --- /dev/null +++ b/server/src/services/local-ai-credentials.ts @@ -0,0 +1,58 @@ +import { readLocalAiCredentialFile } from "./local-ai-credential-file.js"; +import fs from "node:fs/promises"; +import path from "node:path"; +import { readClaudeToken, fetchClaudeQuota } from "@paperclipai/adapter-claude-local/server"; +import { readCodexAuthInfo, fetchCodexQuota } from "@paperclipai/adapter-codex-local/server"; +import { parseGrokAuthPayload, hasUsableGrokAuthValue } from "@paperclipai/adapter-grok-local/server"; +import type { AiProvider } from "@paperclipai/shared"; +import { unprocessable } from "../errors.js"; + +/** Read an owned login home, or an explicitly authorized local-operator import. */ +export async function readVerifiedLocalAiCredential(provider: AiProvider, loginHome?: string): Promise { + if (provider === "openrouter") throw unprocessable("OpenRouter requires an API key."); + if ((provider === "openai" || provider === "xai") && !loginHome) + throw unprocessable("Start a separate local sign-in for this connection before connecting."); + try { + if (provider === "anthropic") { + // Never change process.env or fall back to the server account when an + // authenticated user's isolated login is missing or invalid. + let token: string | null = null; + if (loginHome) { + for (const name of [".credentials.json", "credentials.json"]) { + const raw = await readLocalAiCredentialFile(path.join(loginHome, name)).catch(() => null); + if (!raw) continue; + let parsed; + try { parsed = JSON.parse(raw); } catch { continue; } + const value = parsed?.claudeAiOauth?.accessToken; + if (typeof value === "string" && value.length) { token = value; break; } + } + } else { + token = await readClaudeToken({ allowKeychain: true }); + } + if (!token) throw new Error("Missing login"); + await fetchClaudeQuota(token); + return token; + } + if (provider === "openai") { + const auth = await readCodexAuthInfo(loginHome); + if (!auth?.accessToken || !auth.refreshToken || !auth.idToken) throw new Error("Missing login"); + await fetchCodexQuota(auth.accessToken, auth.accountId); + return JSON.stringify({ tokens: { access_token: auth.accessToken, refresh_token: auth.refreshToken, id_token: auth.idToken, account_id: auth.accountId }, last_refresh: auth.lastRefresh }); + } + const raw = await fs.readFile(path.join(loginHome!, "auth.json"), "utf8"); + const payload = parseGrokAuthPayload(JSON.parse(raw)); + if (!payload || !hasUsableGrokAuthValue(payload.value)) throw new Error("Missing login"); + const response = await fetch("https://api.x.ai/v1/models", { + headers: { Authorization: `Bearer ${payload.value.key}` }, + redirect: "error", signal: AbortSignal.timeout(15000), + }); + await response.body?.cancel(); + if (!response.ok) throw new Error("Invalid login"); + return raw; + } catch { + // Provider/CLI errors may contain credential material; never return them. + throw unprocessable(provider === "anthropic" && !loginHome + ? "Could not verify the local subscription. Run claude auth login in a terminal on the machine running Paperclip, then try Connect again." + : "Could not verify the local subscription. Run the sign-in command shown for this connection, finish signing in, then try Connect again."); + } +} diff --git a/server/src/services/local-ai-login-policy.ts b/server/src/services/local-ai-login-policy.ts new file mode 100644 index 0000000000..51370b9a1b --- /dev/null +++ b/server/src/services/local-ai-login-policy.ts @@ -0,0 +1,12 @@ +import type { DeploymentMode, DeploymentExposure } from "@paperclipai/shared"; + +/** Same server-host boundary as local stdio runtimes. */ +export function supportsLocalAiLogin(options: { + deploymentMode?: DeploymentMode; + deploymentExposure?: DeploymentExposure; + trustedLocalStdioRuntimeHost?: string | null; +}) { + return options.deploymentMode !== "authenticated" || options.deploymentExposure !== "public" || Boolean( + options.trustedLocalStdioRuntimeHost ?? process.env.PAPERCLIP_TRUSTED_MCP_RUNTIME_HOST ?? process.env.PAPERCLIP_TOOL_RUNTIME_TRUSTED_HOST, + ); +} diff --git a/server/src/services/local-ai-login.ts b/server/src/services/local-ai-login.ts new file mode 100644 index 0000000000..8f29a7164a --- /dev/null +++ b/server/src/services/local-ai-login.ts @@ -0,0 +1,205 @@ +import { randomUUID } from "node:crypto"; +import { mkdir, rm, writeFile } from "node:fs/promises"; +import path from "node:path"; +import { and, eq, inArray, lte, sql } from "drizzle-orm"; +import { adapterAuthSessions, ADAPTER_AUTH_SESSION_ACTIVE_STATES, environments, type Db } from "@paperclipai/db"; +import type { AiConnectionLoginIntent, LocalAiLoginAttempt, LocalAiLoginStatus } from "@paperclipai/shared"; +import { resolvePaperclipInstanceRoot } from "../home-paths.js"; +import { notFound, unprocessable } from "../errors.js"; +import { aiConnectionService } from "./ai-connections.js"; +import { readVerifiedLocalAiCredential } from "./local-ai-credentials.js"; +import { logActivity } from "./activity-log.js"; + +const LOCAL_LOGIN_METHOD = "local_subscription"; +const ATTEMPT_DURATION_MS = 30 * 60 * 1000; +function loginHome(id: string) { + return path.join(resolvePaperclipInstanceRoot(), "ai-local-logins", id); +} +const shellQuote = (value: string) => `'${value.replaceAll("'", "'\\''")}'`; +function presentAttempt(id: string, expiresAt: Date, provider: string): LocalAiLoginAttempt { + const directory = loginHome(id); + return { + sessionId: id, expiresAt: expiresAt.toISOString(), + command: provider === "openai" + ? `(export CODEX_HOME=${shellQuote(directory)} && mkdir -p "$CODEX_HOME" && codex -c 'cli_auth_credentials_store="file"' login --device-auth)` + : provider === "anthropic" + ? `(export CLAUDE_CONFIG_DIR=${shellQuote(directory)} && mkdir -p "$CLAUDE_CONFIG_DIR" && claude auth login)` + : `(export GROK_HOME=${shellQuote(directory)} && mkdir -p "$GROK_HOME" && grok login --device-auth)`, + }; +} +async function prepareHome(id: string, provider: string) { + const directory = loginHome(id); + await mkdir(directory, { recursive: true, mode: 0o700 }); + if (provider === "openai") { + // Idempotent: preserve credentials when a user returns to an active attempt. + await writeFile(path.join(directory, "config.toml"), 'cli_auth_credentials_store = "file"\n', { mode: 0o600 }); + } +} +function sameTarget(a: AiConnectionLoginIntent, b: AiConnectionLoginIntent) { + return a.provider === b.provider && a.method === b.method && a.ownership === b.ownership && + a.connectionId === b.connectionId && a.allAgents === b.allAgents && + JSON.stringify([...a.agentIds].sort()) === JSON.stringify([...b.agentIds].sort()); +} + +/** Local terminal sign-ins share the durable attempt/credential lifecycle, but + * never seed their home from the operator's rotating CLI credential. */ +export function localAiLoginService(db: Db) { + async function reapExpired() { + // Bounded batches use the existing expires-at index. Replaying is safe. + const rows = await db.select({ id: adapterAuthSessions.id }).from(adapterAuthSessions) + .where(and(eq(adapterAuthSessions.connectionMethod, LOCAL_LOGIN_METHOD), + lte(adapterAuthSessions.expiresAt, new Date()))) + .orderBy(adapterAuthSessions.expiresAt).limit(100); + for (const row of rows) { + await db.transaction(async (tx) => { + const [session] = await tx.select().from(adapterAuthSessions) + .where(eq(adapterAuthSessions.id, row.id)).for("update"); + if (!session || !session.expiresAt || session.expiresAt.getTime() > Date.now()) return; + await rm(loginHome(row.id), { recursive: true, force: true }); + await tx.update(adapterAuthSessions).set({ + status: session.connectionId ? "authenticated" : "timed_out", + expiresAt: null, finishedAt: session.finishedAt ?? new Date(), updatedAt: new Date(), + }).where(eq(adapterAuthSessions.id, row.id)); + }); + } + } + + async function start(companyId: string, userId: string, intent: AiConnectionLoginIntent, restart = false): Promise { + if (intent.provider !== "openai" && intent.provider !== "xai" && intent.provider !== "anthropic") + throw unprocessable("This provider does not use a separate local login home."); + await reapExpired(); + return db.transaction(async (tx) => { + await tx.execute(sql`select pg_advisory_xact_lock(hashtextextended(${`ai-local-login:${companyId}:${userId}:${intent.provider}`}, 0))`); + const adapterType = intent.provider === "openai" ? "codex_local" : intent.provider === "anthropic" ? "claude_local" : "grok_local"; + const [existing] = await tx.select().from(adapterAuthSessions).where(and( + eq(adapterAuthSessions.companyId, companyId), eq(adapterAuthSessions.startedByUserId, userId), + eq(adapterAuthSessions.adapterType, adapterType), + inArray(adapterAuthSessions.status, [...ADAPTER_AUTH_SESSION_ACTIVE_STATES]), + )).for("update"); + if (existing) { + if (!restart && existing.connectionMethod === LOCAL_LOGIN_METHOD && existing.status === "waiting_for_user" && + existing.aiConnection && sameTarget(existing.aiConnection, intent) && + existing.expiresAt && existing.expiresAt.getTime() > Date.now()) { + await prepareHome(existing.id, intent.provider); + return presentAttempt(existing.id, existing.expiresAt, intent.provider); + } + if (!restart || existing.connectionMethod !== LOCAL_LOGIN_METHOD) + throw unprocessable("Another sign-in is still open. Finish it, or choose Start sign-in again to replace a local attempt."); + await rm(loginHome(existing.id), { recursive: true, force: true }); + await tx.update(adapterAuthSessions).set({ status: "cancelled", finishedAt: new Date(), updatedAt: new Date() }) + .where(eq(adapterAuthSessions.id, existing.id)); + await logActivity(tx as unknown as Db, { + companyId, actorType: "user", actorId: userId, action: "ai_connection.local_login_cancelled", + entityType: "adapter_auth_session", entityId: existing.id, details: { provider: intent.provider }, + }); + } + const [environment] = await tx.select().from(environments) + .where(and(eq(environments.driver, "local"), eq(environments.status, "active"))).limit(1); + if (!environment) throw unprocessable("No active local environment is available."); + const id = randomUUID(); + const directory = loginHome(id); + const expiresAt = new Date(Date.now() + ATTEMPT_DURATION_MS); + await prepareHome(id, intent.provider); + try { + await tx.insert(adapterAuthSessions).values({ + id, publicSessionId: id, companyId, environmentId: environment.id, + adapterType, + startedByUserId: userId, aiConnection: intent, + connectionMethod: LOCAL_LOGIN_METHOD, status: "waiting_for_user", expiresAt, + }); + await logActivity(tx as unknown as Db, { + companyId, actorType: "user", actorId: userId, action: "ai_connection.local_login_started", + entityType: "adapter_auth_session", entityId: id, details: { provider: intent.provider }, + }); + } catch (error) { + await rm(directory, { recursive: true, force: true }); + if ((error as { cause?: { code?: string }; code?: string }).cause?.code === "23505" || + (error as { code?: string }).code === "23505") + throw unprocessable("Another sign-in is still open. Finish or cancel it before starting again."); + throw error; + } + return presentAttempt(id, expiresAt, intent.provider); + }); + } + + // Read-only credential detection: never saves a connection or refreshes another + // login. Scope and intent are checked before touching an attempt's directory. + async function check(companyId: string, userId: string, intent: AiConnectionLoginIntent, id?: string): Promise { + let directory: string | undefined; + if (id || intent.provider !== "anthropic") { + if (!id) throw unprocessable("Start local sign-in before checking this account."); + const [session] = await db.select().from(adapterAuthSessions).where(and( + eq(adapterAuthSessions.id, id), eq(adapterAuthSessions.companyId, companyId), + eq(adapterAuthSessions.startedByUserId, userId), + eq(adapterAuthSessions.connectionMethod, LOCAL_LOGIN_METHOD), + )); + if (!session?.aiConnection || !sameTarget(session.aiConnection, intent)) + throw notFound("Local sign-in attempt not found for this connection."); + if (session.status !== "waiting_for_user" || !session.expiresAt || session.expiresAt.getTime() <= Date.now()) + return { status: "expired" }; + directory = loginHome(id); + } + try { + await readVerifiedLocalAiCredential(intent.provider, directory); + return { status: "ready" }; + } catch { + return { status: "sign_in_required" }; + } + } + + async function complete(companyId: string, userId: string, id: string, intent: AiConnectionLoginIntent) { + const result = await db.transaction(async (tx) => { + const [session] = await tx.select().from(adapterAuthSessions).where(and( + eq(adapterAuthSessions.id, id), eq(adapterAuthSessions.companyId, companyId), + eq(adapterAuthSessions.startedByUserId, userId), + // Successful completion replaces connectionMethod with subscription. + sql`${adapterAuthSessions.providerLeaseId} is null`, + )).for("update"); + if (!session || !session.aiConnection || !sameTarget(session.aiConnection, intent)) + throw notFound("Local sign-in attempt not found for this connection."); + if (session.connectionId && session.connectionGrantId) + return { connectionId: session.connectionId, grantId: session.connectionGrantId }; + if (session.connectionMethod !== LOCAL_LOGIN_METHOD || session.status !== "waiting_for_user" || + !session.expiresAt || session.expiresAt.getTime() <= Date.now()) + throw unprocessable("This sign-in attempt has expired or was cancelled. Start sign-in again."); + const credential = await readVerifiedLocalAiCredential(intent.provider, loginHome(id)); + await tx.update(adapterAuthSessions).set({ status: "promoting", updatedAt: new Date() }) + .where(eq(adapterAuthSessions.id, id)); + // Nested transaction is a savepoint on this same connection. Holding the + // attempt lock makes completion/cancellation/restart retries idempotent. + const saved = await aiConnectionService(tx as unknown as Db) + .save(companyId, userId, intent, credential, id, session.createdAt); + await tx.update(adapterAuthSessions).set({ + status: "authenticated", connectionMethod: LOCAL_LOGIN_METHOD, + finishedAt: new Date(), updatedAt: new Date(), + }).where(eq(adapterAuthSessions.id, id)); + return saved; + }); + // Failed cleanup can be retried by the same completed attempt or reaper. + await rm(loginHome(id), { recursive: true, force: true }); + return result; + } + + async function cancel(companyId: string, userId: string, id: string) { + await db.transaction(async (tx) => { + const [session] = await tx.select().from(adapterAuthSessions).where(and( + eq(adapterAuthSessions.id, id), eq(adapterAuthSessions.companyId, companyId), + eq(adapterAuthSessions.startedByUserId, userId), + eq(adapterAuthSessions.connectionMethod, LOCAL_LOGIN_METHOD), + )).for("update"); + if (!session) throw notFound("Local sign-in attempt not found."); + await rm(loginHome(id), { recursive: true, force: true }); + if (!session.connectionId && session.status !== "cancelled") { + await tx.update(adapterAuthSessions).set({ + status: "cancelled", finishedAt: new Date(), updatedAt: new Date(), + }).where(eq(adapterAuthSessions.id, id)); + await logActivity(tx as unknown as Db, { + companyId, actorType: "user", actorId: userId, action: "ai_connection.local_login_cancelled", + entityType: "adapter_auth_session", entityId: id, + details: { provider: session.aiConnection?.provider }, + }); + } + }); + } + return { start, check, complete, cancel, reapExpired }; +} diff --git a/server/src/services/native-runtime/chat-attachment-reuse.ts b/server/src/services/native-runtime/chat-attachment-reuse.ts index d5a800280b..ecfd9b6256 100644 --- a/server/src/services/native-runtime/chat-attachment-reuse.ts +++ b/server/src/services/native-runtime/chat-attachment-reuse.ts @@ -422,6 +422,7 @@ export async function authorizeChatConversationForBoundRun( "discord", "microsoft-teams", "telegram", + "imessage-photon", ].find( (candidate) => source === `chat:${candidate}` || source === `chat:${candidate}:recovery`, @@ -552,6 +553,7 @@ function externalChatWaitCandidate( "discord", "microsoft-teams", "telegram", + "imessage-photon", ].find( (candidate) => source === `chat:${candidate}` || source === `chat:${candidate}:recovery`, diff --git a/server/src/services/native-runtime/current-wake-comments.ts b/server/src/services/native-runtime/current-wake-comments.ts index a19c00bfaf..50a69712db 100644 --- a/server/src/services/native-runtime/current-wake-comments.ts +++ b/server/src/services/native-runtime/current-wake-comments.ts @@ -32,6 +32,7 @@ const EXTERNAL_CHAT_PROVIDERS = new Set([ "discord", "microsoft-teams", "telegram", + "imessage-photon", ]); const ATTACHMENT_OMISSION_REASONS = new Set([ "attachment_limit", diff --git a/server/src/services/native-runtime/external-chat-question-response.ts b/server/src/services/native-runtime/external-chat-question-response.ts index aad176d51c..233acfa700 100644 --- a/server/src/services/native-runtime/external-chat-question-response.ts +++ b/server/src/services/native-runtime/external-chat-question-response.ts @@ -1,4 +1,5 @@ -import { and, eq, inArray, notExists, sql } from "drizzle-orm"; +import { photonAnswersMatch } from "../photon/interactions.js"; +import { and, or, eq, inArray, notExists, sql } from "drizzle-orm"; import type { Db } from "@paperclipai/db"; import { agentWakeupRequests, @@ -338,7 +339,7 @@ async function resolveQuestionResponseChain( const provider = parent?.provider ?? ( - ["slack", "github", "discord", "microsoft-teams", "telegram"] as const + ["slack", "github", "discord", "microsoft-teams", "telegram", "imessage-photon"] as const ).find( (candidate) => sourceContext.source === `chat:${candidate}` || @@ -448,7 +449,7 @@ async function resolveQuestionResponseChain( eq(chatDeliveries.companyId, chatActions.companyId), eq(chatDeliveries.endpointId, chatActions.endpointId), eq(chatDeliveries.conversationId, chatActions.conversationId), - eq(chatDeliveries.principalId, chatActions.principalId), + or(eq(chatActions.kind, "photon_interaction"), eq(chatDeliveries.principalId, chatActions.principalId)), ), ) .innerJoin( @@ -485,7 +486,7 @@ async function resolveQuestionResponseChain( .where( and( eq(chatActions.companyId, binding.companyId), - inArray(chatActions.kind, ["question_answer", "question_form_submit"]), + inArray(chatActions.kind, ["question_answer", "question_form_submit", "photon_interaction"]), eq(chatActions.status, "processed"), sql`${chatActions.payload}->>'interactionId' = ${interaction.id}`, sql`${chatActions.result}->>'interactionId' = ${interaction.id}`, @@ -686,6 +687,8 @@ async function resolveQuestionResponseChain( ) ) return null; + } else if (action.kind === "photon_interaction") { + if (provider !== "imessage-photon" || !photonAnswersMatch(interaction as unknown as AskUserQuestionsInteraction, action.result) || action.payload.version !== 1 || action.payload.sessionGeneration !== conversation.sessionGeneration || typeof action.payload.expiresAt !== "string" || Date.parse(action.payload.expiresAt) <= interaction.resolvedAt.getTime() || !Number.isFinite(Date.parse(action.payload.expiresAt))) return null; } else if ( !completedQuestionFormMatchesInteraction( interaction as unknown as AskUserQuestionsInteraction, @@ -697,12 +700,13 @@ async function resolveQuestionResponseChain( if ( inbound.state !== "processed" || comment.deletedAt !== null || - comment.authorUserId !== interaction.resolvedByUserId || + (action?.kind !== "photon_interaction" && comment.authorUserId !== interaction.resolvedByUserId) || conversation.issueId !== binding.issueId || !["active", "waiting"].includes(conversation.state) || endpoint.provider !== provider || endpoint.assignedAgentId !== binding.agentId || - endpoint.status !== "active" || + (endpoint.status !== "active" && + !(provider === "imessage-photon" && endpoint.status === "verifying" && record(endpoint.setup).step === "test")) || publication.state !== "published" || !publication.providerMessageId || publication.issueId !== binding.issueId || diff --git a/server/src/services/native-runtime/native-session-executor.ts b/server/src/services/native-runtime/native-session-executor.ts index f067e6991a..15e81dd201 100644 --- a/server/src/services/native-runtime/native-session-executor.ts +++ b/server/src/services/native-runtime/native-session-executor.ts @@ -1,3 +1,4 @@ +import { copyBackCodexAuth } from "@paperclipai/adapter-codex-local/server"; import { nativeCompletionFeedback } from "./native-completion-feedback.js"; import { PROCESS_START_REQUESTED } from "../native-local-process-stop.js"; import { remoteLeaseCleanupScope } from "../remote-execution-termination.js"; @@ -6684,6 +6685,8 @@ export async function executePaperclipNativeSession(input: { preparationSpans?: NativeRunHistoricalSpan[]; /** Resolved adapter env; the runner transport applies a provider allowlist before spawn. */ runnerEnvironment?: NodeJS.ProcessEnv; + /** Private grant materialization; never a user-configured host path. */ + managedAiCredentialHome?: string; runnerExecutionTarget?: AdapterExecutionTarget | null; /** Resolved per-run authorization; not an independent instance setting. */ runnerIngressAuthorized?: boolean; @@ -9619,6 +9622,8 @@ export async function createRunnerdBackend(input: { startedAt: string; }) => Promise; runnerEnvironment?: NodeJS.ProcessEnv; + /** Private grant materialization; never a user-configured host path. */ + managedAiCredentialHome?: string; runnerExecutionTarget?: AdapterExecutionTarget | null; /** Resolved per-run authorization; not an independent instance setting. */ runnerIngressAuthorized?: boolean; @@ -11714,6 +11719,34 @@ async function createRunnerdBackendWithinSessionClaim( }, }).transport, }); + const wrapManagedSession = (session: NativeSession): NativeSession => { + if (!input.managedAiCredentialHome || input.execution.provider.kind !== "codex") return session; + const close = session.close.bind(session); + let copied = false; + session.close = async (closeInput) => { + await close(closeInput); + if (copied) return; + copied = true; + const remoteAuth = remoteRunnerFilesystemRoot ? posix.join(remoteRunnerFilesystemRoot, "codex-home", "auth.json") : null; + const localAuth = join(root, "codex-home", "auth.json"); + try { + await copyBackCodexAuth({ + hostAuthPath: join(input.managedAiCredentialHome!, "auth.json"), + readSandboxAuth: async () => { + if (!remoteAuth || !remoteCommandRunner) return readFileSync(localAuth); + const result = await remoteCommandRunner.execute({ command: "base64", args: [remoteAuth], bypassSession: true, timeoutMs: 10000 }); + if (result.exitCode !== 0 || result.timedOut) throw new Error("AI credential copy-back failed"); + return Buffer.from(result.stdout, "base64"); + }, + log: () => {}, + }); + } finally { + rmSync(localAuth, { force: true }); + if (remoteAuth && remoteCommandRunner) await remoteCommandRunner.execute({ command: "rm", args: ["-f", "--", remoteAuth], bypassSession: true, timeoutMs: 10000 }); + } + }; + return session; + }; const priorAuthorityEpoch = sessionToolAuthorityEpochs.get(sessionScopeId); if (priorAuthorityEpoch && priorAuthorityEpoch !== authorityEpoch) { priorAuthorityEpoch.revoke(); @@ -11721,14 +11754,11 @@ async function createRunnerdBackendWithinSessionClaim( sessionToolAuthorityEpochs.set(sessionScopeId, authorityEpoch); return { descriptor: () => backend.descriptor(), - openSession: (sessionInput) => backend.openSession(sessionInput), - recoverSession: (snapshot, options) => - backend.recoverSession - ? backend.recoverSession(snapshot, options) - : Promise.resolve({ - recovered: false, - reason: "driver does not support recovery", - }), + openSession: async (sessionInput) => wrapManagedSession(await backend.openSession(sessionInput)), + recoverSession: async (snapshot, options) => { + const result = backend.recoverSession ? await backend.recoverSession(snapshot, options) : { recovered: false, reason: "driver does not support recovery" }; + return result.session ? { ...result, session: wrapManagedSession(result.session) } : result; + }, openReplacementSession: async (sessionInput) => { await measureNativeRunnerSpan( input.trace, @@ -11736,7 +11766,7 @@ async function createRunnerdBackendWithinSessionClaim( archiveContinuityState, { parentName: "native.session.execute" }, ); - return backend.openSession(sessionInput); + return wrapManagedSession(await backend.openSession(sessionInput)); }, } satisfies NativeSessionBackend; } diff --git a/server/src/services/openrouter-models.test.ts b/server/src/services/openrouter-models.test.ts new file mode 100644 index 0000000000..a4335bd223 --- /dev/null +++ b/server/src/services/openrouter-models.test.ts @@ -0,0 +1,25 @@ +import { afterEach, expect, it, vi } from "vitest"; + +afterEach(() => { vi.unstubAllGlobals(); vi.resetModules(); }); + +it("lists and caches public OpenRouter models without sending credentials", async () => { + const fetch = vi.fn().mockResolvedValue({ ok: true, json: async () => ({ data: [ + { id: "anthropic/claude-sonnet-4.5", name: "Claude Sonnet" }, { id: 42 }, + ] }) }); + vi.stubGlobal("fetch", fetch); + const { listOpenRouterModels } = await import("./openrouter-models.js"); + expect(await listOpenRouterModels()).toEqual([{ id: "openrouter/anthropic/claude-sonnet-4.5", label: "Claude Sonnet" }]); + await listOpenRouterModels(); + expect(fetch).toHaveBeenCalledTimes(1); + expect(fetch).toHaveBeenCalledWith("https://openrouter.ai/api/v1/models", { signal: expect.any(AbortSignal) }); + await listOpenRouterModels(true); + expect(fetch).toHaveBeenCalledTimes(2); +}); + +it("allows retry after a public catalog failure", async () => { + const fetch = vi.fn().mockResolvedValueOnce({ ok: false }).mockResolvedValueOnce({ ok: true, json: async () => ({ data: [] }) }); + vi.stubGlobal("fetch", fetch); + const { listOpenRouterModels } = await import("./openrouter-models.js"); + await expect(listOpenRouterModels()).rejects.toThrow("Retry or enter a model ID manually"); + await expect(listOpenRouterModels()).resolves.toEqual([]); +}); diff --git a/server/src/services/openrouter-models.ts b/server/src/services/openrouter-models.ts new file mode 100644 index 0000000000..a0c9414caf --- /dev/null +++ b/server/src/services/openrouter-models.ts @@ -0,0 +1,22 @@ +import type { AdapterModel } from "@paperclipai/adapter-utils"; + +let cached: { until: number; models: AdapterModel[] } | undefined; +let pending: Promise | undefined; + +/** OpenRouter's public catalog does not require access to anyone's credentials. */ +export async function listOpenRouterModels(refresh = false): Promise { + if (!refresh && cached && cached.until > Date.now()) return cached.models; + if (pending) return pending; + pending = (async () => { + const response = await fetch("https://openrouter.ai/api/v1/models", { signal: AbortSignal.timeout(10_000) }); + if (!response.ok) throw new Error("Could not load OpenRouter models. Retry or enter a model ID manually."); + const body = await response.json() as { data?: Array<{ id?: unknown; name?: unknown }> }; + if (!Array.isArray(body.data)) throw new Error("OpenRouter returned an invalid model catalog."); + const models = body.data.flatMap(model => typeof model.id === "string" && model.id.includes("/") + ? [{ id: `openrouter/${model.id}`, label: typeof model.name === "string" ? model.name : model.id }] + : []).sort((a, b) => a.label.localeCompare(b.label)); + cached = { until: Date.now() + 60_000, models }; + return models; + })(); + try { return await pending; } finally { pending = undefined; } +} diff --git a/server/src/services/photon/adapter.ts b/server/src/services/photon/adapter.ts new file mode 100644 index 0000000000..9285ea1b6e --- /dev/null +++ b/server/src/services/photon/adapter.ts @@ -0,0 +1,576 @@ +import { PhotonRecoveryTransport } from "./recovery-transport.js"; +import { createHash } from "node:crypto"; +import { + createGrpcClient, + type GrpcAdvancedIMessage, + type Message as PhotonMessage, + type Chat as PhotonChat, +} from "@photon-ai/advanced-imessage"; +import { + Message, + parseMarkdown, + stringifyMarkdown, + type Adapter, + type AdapterPostableMessage, + type ChatInstance, + type FileUpload, + type FormattedContent, + type FetchOptions, + type ThreadInfo, + type Attachment, +} from "chat"; +import { + PhotonLineAuthentication, + PhotonError, + photonFailure, +} from "./cloud.js"; +import { PhotonState } from "./state.js"; +import { + downloadPhotonAttachment, + type PhotonAttachmentLocator, +} from "./attachments.js"; +import { MAX_ATTACHMENT_BYTES } from "../../attachment-types.js"; + +export interface PhotonThread { + lineId: string; + chatGuid: string; + isGroup: boolean; +} +interface SendRecord { + schema: 1; + digest: string; + phase: "prepared" | "uploading" | "uploaded" | "sending" | "sent"; + attachmentGuid?: string; + messageGuid?: string; +} +export function photonThreadId(value: PhotonThread): string { + return `imessage-photon:${value.lineId}:${value.isGroup ? "g" : "d"}:${Buffer.from(value.chatGuid).toString("base64url")}`; +} +export function parsePhotonThreadId(value: string): PhotonThread { + const match = + /^imessage-photon:([a-zA-Z0-9-]{1,63}):(d|g):([a-zA-Z0-9_-]{1,1024})$/.exec( + value, + ); + if (!match) throw new Error("Invalid Photon conversation identity"); + const chatGuid = Buffer.from(match[3], "base64url").toString("utf8"); + if (!chatGuid || Buffer.from(chatGuid).toString("base64url") !== match[3]) + throw new Error("Invalid Photon chat identity"); + return { lineId: match[1], chatGuid, isGroup: match[2] === "g" }; +} +/** Closed quote context survives durable admission without retaining SDK objects. */ +export function photonReplyReference( + raw: unknown, +): { guid: string; part?: string } | null { + if (!raw || typeof raw !== "object") return null; + const value = raw as Partial; + const guid = value.replyTargetGuid ?? value.threadOriginatorGuid; + if (typeof guid !== "string" || !guid || guid.length > 512) return null; + return { + guid, + ...(typeof value.threadOriginatorPart === "string" && + value.threadOriginatorPart.length <= 64 + ? { part: value.threadOriginatorPart } + : {}), + }; +} +export function splitPhotonText(text: string, maximum = 4000): string[] { + const points = Array.from(text); + const result: string[] = []; + while (points.length > maximum) { + const candidate = points.slice(0, maximum).join(""); + const paragraph = candidate.lastIndexOf("\n\n"); + const count = + paragraph >= maximum / 2 + ? Array.from(candidate.slice(0, paragraph + 2)).length + : maximum; + result.push(points.splice(0, count).join("")); + } + if (points.length) result.push(points.join("")); + return result; +} +function textOf(message: AdapterPostableMessage): string { + if (typeof message === "string") return message; + if ("markdown" in message) return message.markdown; + if ("raw" in message) return message.raw; + if ("ast" in message) return stringifyMarkdown(message.ast); + if ("fallbackText" in message) return message.fallbackText ?? ""; + throw new Error("Photon requires a reviewed text or poll publication"); +} + +export class PhotonChatAdapter implements Adapter { + readonly name = "imessage-photon"; + readonly lockScope = "channel" as const; + readonly botUserId: string; + readonly client: GrpcAdvancedIMessage; + private closed = false; + private recovery?: PhotonRecoveryTransport; + private readonly typing = new Map>(); + typingGuard?: (activeThreadId?: string) => Promise; + recoveryStream(sequence?: number) { + this.recovery ??= new PhotonRecoveryTransport(this.authentication); + return this.recovery.catchUp(sequence); + } + constructor( + readonly userName: string, + readonly authentication: PhotonLineAuthentication, + readonly state: PhotonState, + client?: GrpcAdvancedIMessage, + ) { + this.botUserId = authentication.identity.phoneNumber; + this.client = + client ?? + createGrpcClient({ + address: authentication.address, + token: () => authentication.token(), + tls: true, + retry: false, + autoIdempotency: false, + timeout: 25_000, + channelOptions: { + "grpc.max_receive_message_length": MAX_ATTACHMENT_BYTES + 1024 * 1024, + }, + }); + } + async initialize(_chat: ChatInstance): Promise { + await this.authentication.token(); + } + async disconnect(): Promise { + this.closed = true; + for (const timer of this.typing.values()) clearTimeout(timer); + this.typing.clear(); + this.authentication.retire(); + this.recovery?.close(); + await this.client.close(); + } + encodeThreadId(value: PhotonThread): string { + if (value.isGroup && this.authentication.identity.allocation === "shared") + throw new PhotonError("rejected", "Photon shared channels support direct messages only"); + if (value.lineId !== this.authentication.identity.lineId) + throw new Error("Wrong Photon line"); + return photonThreadId(value); + } + decodeThreadId(id: string): PhotonThread { + const value = parsePhotonThreadId(id); + if (value.isGroup && this.authentication.identity.allocation === "shared") + throw new PhotonError("rejected", "Photon shared channels support direct messages only"); + if (value.lineId !== this.authentication.identity.lineId) + throw new Error("Wrong Photon line"); + return value; + } + channelIdFromThreadId(id: string): string { + this.decodeThreadId(id); + return id; + } + isDM(id: string): boolean { + return !this.decodeThreadId(id).isGroup; + } + async chatInfo(id: string): Promise { + if (this.closed) throw new Error("Photon runtime retired"); + const identity = this.decodeThreadId(id); + const chat = await this.client.chats + .get(identity.chatGuid) + .catch((error) => { + throw photonFailure(error); + }); + if ( + chat.guid !== identity.chatGuid || + chat.isGroup !== identity.isGroup || + chat.service !== "iMessage" + ) + throw new Error("Photon conversation identity changed"); + return chat; + } + async fetchThread(id: string): Promise { + const chat = await this.chatInfo(id); + return { + id, + channelId: id, + channelName: + chat.displayName || chat.participants.map((p) => p.address).join(", "), + isDM: !chat.isGroup, + metadata: { + participants: chat.participants.map((p) => ({ + address: p.address, + service: p.service, + })), + isArchived: chat.isArchived, + }, + }; + } + async fetchChannelInfo(id: string) { + const info = await this.fetchThread(id); + return { + id, + name: info.channelName, + isDM: info.isDM, + metadata: info.metadata, + }; + } + parseMessage(raw: PhotonMessage): Message { + if (raw.chatGuids.length !== 1) + throw new Error( + "Photon message must have one authenticated conversation", + ); + // The receiver supplies the authoritative chat shape; fetch paths set it too. + const isGroup = (raw as PhotonMessage & { paperclipIsGroup?: boolean }) + .paperclipIsGroup; + if (typeof isGroup !== "boolean") + throw new Error("Photon message is missing its authenticated chat shape"); + const chatGuid = raw.chatGuids[0]; + const threadId = this.encodeThreadId({ + lineId: this.authentication.identity.lineId, + chatGuid, + isGroup, + }); + const address = raw.sender?.address ?? (raw.isFromMe ? this.botUserId : ""); + const service = raw.sender?.service; + const authorId = raw.isFromMe ? this.botUserId : `${service}:${address}`; + const attachments: Attachment[] = raw.content.attachments + .filter((a) => !a.isHidden && !a.isSticker) + .map((a) => { + const locator: PhotonAttachmentLocator = { + kind: "photon_attachment", + lineId: this.authentication.identity.lineId, + chatGuid, + messageGuid: raw.guid, + attachmentGuid: a.guid, + }; + return { + type: a.mimeType.startsWith("image/") + ? "image" + : a.mimeType.startsWith("audio/") + ? "audio" + : a.mimeType.startsWith("video/") + ? "video" + : "file", + name: a.fileName, + mimeType: a.mimeType, + size: a.totalBytes, + fetchMetadata: { + kind: locator.kind, + lineId: locator.lineId, + chatGuid: locator.chatGuid, + messageGuid: locator.messageGuid, + attachmentGuid: locator.attachmentGuid, + }, + fetchData: () => + downloadPhotonAttachment( + this.client, + this.authentication.identity.lineId, + locator, + this.authentication.identity.allocation, + ), + }; + }); + const text = raw.content.text ?? ""; + return new Message({ + id: raw.guid, + threadId, + text, + formatted: parseMarkdown(text), + raw, + attachments, + author: { + userId: authorId, + userName: address, + fullName: address, + isBot: false, + isMe: raw.isFromMe, + isSystem: + raw.isSystemMessage || + raw.isServiceMessage || + !address || + (service !== "iMessage" && !raw.isFromMe), + }, + metadata: { + dateSent: new Date(raw.dateCreated), + edited: !!raw.dateEdited, + editedAt: raw.dateEdited ? new Date(raw.dateEdited) : undefined, + }, + }); + } + normalize(raw: PhotonMessage, chat: PhotonChat): Message { + if (!raw.chatGuids.includes(chat.guid)) + throw new Error("Photon message belongs to another chat"); + return this.parseMessage({ + ...raw, + chatGuids: [chat.guid], + paperclipIsGroup: chat.isGroup, + } as PhotonMessage); + } + async fetchMessage(id: string, messageId: string) { + const chat = await this.chatInfo(id); + return this.normalize( + await this.client.messages.get(messageId).catch((error) => { + throw photonFailure(error); + }), + chat, + ); + } + async fetchMessages(id: string, options?: FetchOptions) { + const chat = await this.chatInfo(id); + const page = await this.client.messages + .listInChat(chat.guid, { + pageSize: Math.min(options?.limit ?? 50, 100), + pageToken: options?.cursor, + }) + .catch((error) => { + throw photonFailure(error); + }); + return { + messages: page.messages + .map((message) => this.normalize(message, chat)) + .sort( + (a, b) => + a.metadata.dateSent.getTime() - b.metadata.dateSent.getTime(), + ), + nextCursor: page.nextPageToken, + }; + } + async getUser(userId: string) { + return { + userId, + fullName: userId.replace(/^iMessage:/, ""), + userName: userId.replace(/^iMessage:/, ""), + isBot: false, + }; + } + async handleWebhook(): Promise { + return new Response("Photon uses authenticated streams", { status: 405 }); + } + renderFormatted(content: FormattedContent): string { + return stringifyMarkdown(content); + } + async startTyping(id: string): Promise { + await this.refreshTyping(id, false); + } + private async refreshTyping(id: string, refresh: boolean): Promise { + if (this.closed) return; + if (this.typing.has(id)) clearTimeout(this.typing.get(id)); + await this.typingGuard?.(refresh ? id : undefined); + await this.client.chats + .setTyping(this.decodeThreadId(id).chatGuid, true) + .catch((error) => { + throw photonFailure(error); + }); + if (this.closed) return; + // Refresh while work is running; final/prompt publication or runtime + // retirement clears this timer. Every refresh checks endpoint ownership. + const timer = setTimeout(() => { + this.typing.delete(id); + void this.refreshTyping(id, true).catch(() => {}); + }, 8_000); + timer.unref(); + this.typing.set(id, timer); + } + async endTyping(id: string): Promise { + if (this.typing.has(id)) clearTimeout(this.typing.get(id)); + this.typing.delete(id); + if (!this.closed) + await this.client.chats + .setTyping(this.decodeThreadId(id).chatGuid, false) + .catch(() => {}); + } + async postMessage( + _id: string, + _message: AdapterPostableMessage, + ): Promise { + throw new Error( + "Photon sends require an immutable Paperclip publication identity", + ); + } + async editMessage( + _id: string, + _messageId: string, + _message: AdapterPostableMessage, + ): Promise { + throw new Error( + "Photon edits require an immutable Paperclip publication identity", + ); + } + async deleteMessage(): Promise { + throw new Error("Photon message deletion is not supported"); + } + async addReaction(): Promise { + /* Receipt reactions are intentionally not published. */ + } + async removeReaction(): Promise { + /* Reactions never represent approvals. */ + } + + async publish( + id: string, + publicationId: string, + message: AdapterPostableMessage, + options: { + replyTo?: string; + replaceMessageId?: string; + retryUnknown?: boolean; + assertCurrent(): Promise; + }, + ): Promise<{ id: string; messageIds: string[] }> { + const chat = await this.chatInfo(id); + if (chat.isArchived) throw new Error("Photon conversation is unavailable"); + const text = textOf(message); + const files = + typeof message !== "string" && "files" in message + ? (message.files ?? []) + : []; + const parts = splitPhotonText(text); + if (options.replaceMessageId && (parts.length !== 1 || files.length)) + throw new Error("Photon cannot edit a multipart publication"); + let lastId: string | undefined; + const messageIds: string[] = []; + for (let index = 0; index < parts.length; index++) { + lastId = await this.sendPart( + publicationId, + `text-${index}`, + { + chatGuid: chat.guid, + text: parts[index], + replyTo: options.replyTo, + replaceMessageId: options.replaceMessageId, + }, + undefined, + options, + ); + messageIds.push(lastId); + } + for (let index = 0; index < files.length; index++) { + const file = files[index]; + const bytes = + file.data instanceof Blob + ? Buffer.from(await file.data.arrayBuffer()) + : Buffer.from(file.data as ArrayBuffer); + if (bytes.length > MAX_ATTACHMENT_BYTES) + throw new Error("Attachment exceeds the configured size limit"); + lastId = await this.sendPart( + publicationId, + `file-${index}`, + { + chatGuid: chat.guid, + filename: file.filename, + sha256: createHash("sha256").update(bytes).digest("hex"), + replyTo: options.replyTo, + }, + { ...file, data: bytes }, + options, + ); + messageIds.push(lastId); + } + if (!lastId) throw new Error("Photon publication is empty"); + await this.endTyping(id); + return { id: lastId, messageIds }; + } + private async sendPart( + publicationId: string, + part: string, + payload: { + chatGuid: string; + text?: string; + filename?: string; + sha256?: string; + replyTo?: string; + replaceMessageId?: string; + }, + file: FileUpload | undefined, + options: { retryUnknown?: boolean; assertCurrent(): Promise }, + ): Promise { + const key = `send:${publicationId}:${part}`; + const digest = createHash("sha256") + .update(JSON.stringify(payload)) + .digest("hex"); + let record = await this.state.update(key, (current) => { + if (current && current.digest !== digest) + throw new Error("Photon publication payload changed after preparation"); + return current ?? { schema: 1, digest, phase: "prepared" }; + }); + if (record.phase === "sent" && record.messageGuid) + return record.messageGuid; + if ( + (record.phase === "sending" || record.phase === "uploading") && + !options.retryUnknown + ) + throw new PhotonError( + "delivery_unknown", + "Photon delivery is unknown; resolve this publication before retrying", + ); + const save = async (patch: Partial) => { + record = await this.state.update(key, (current) => { + if (!current || current.digest !== digest) + throw new Error("Photon send identity changed"); + return { ...current, ...patch }; + }); + }; + if (file && !record.attachmentGuid) { + await options.assertCurrent(); + await save({ phase: "uploading" }); + try { + const uploaded = await this.client.attachments.upload({ + fileName: file.filename, + data: file.data as Buffer, + }); + if (!uploaded.attachment.guid) + throw new PhotonError( + "delivery_unknown", + "Photon upload receipt is missing", + ); + await save({ + phase: "uploaded", + attachmentGuid: uploaded.attachment.guid, + }); + } catch (error) { + const failure = photonFailure(error, true); + if (failure.code !== "delivery_unknown") + await save({ phase: "prepared" }); + throw failure; + } + } + await options.assertCurrent(); + if (payload.replaceMessageId) { + const original = await this.client.messages.get(payload.replaceMessageId); + if ( + !original.isFromMe || + !original.chatGuids.includes(payload.chatGuid) || + Date.now() - new Date(original.dateCreated).getTime() >= 15 * 60_000 + ) + throw new PhotonError( + "rejected", + "The iMessage edit window expired; stage a correction as a new publication", + ); + } + await save({ phase: "sending" }); + const clientMessageId = createHash("sha256") + .update(`${this.state.scope.endpointId}:${publicationId}:${part}`) + .digest("hex"); + try { + const result = record.attachmentGuid + ? await this.client.messages.sendAttachment( + payload.chatGuid, + record.attachmentGuid, + { clientMessageId, replyTo: payload.replyTo }, + ) + : payload.replaceMessageId + ? await this.client.messages.edit( + payload.chatGuid, + payload.replaceMessageId, + payload.text!, + { clientMessageId }, + ) + : await this.client.messages.sendText( + payload.chatGuid, + payload.text!, + { clientMessageId, replyTo: payload.replyTo }, + ); + if (!result.guid || !result.chatGuids.includes(payload.chatGuid)) + throw new Error("Photon returned an invalid send receipt"); + await save({ phase: "sent", messageGuid: result.guid }); + return result.guid; + } catch (error) { + const failure = photonFailure(error, true); + if (failure.code !== "delivery_unknown") + await save({ phase: record.attachmentGuid ? "uploaded" : "prepared" }); + throw failure; + } + } +} diff --git a/server/src/services/photon/attachments.ts b/server/src/services/photon/attachments.ts new file mode 100644 index 0000000000..b2446d8bfc --- /dev/null +++ b/server/src/services/photon/attachments.ts @@ -0,0 +1,208 @@ +import type { Attachment, Message as ChatMessage } from "chat"; +import type { + GrpcAdvancedIMessage, + CompanionInfo, + Message as PhotonMessage, +} from "@photon-ai/advanced-imessage"; +import { z } from "zod"; +import { MAX_ATTACHMENT_BYTES } from "../../attachment-types.js"; +import { PhotonError, photonFailure } from "./cloud.js"; +const guid = z.string().min(1).max(512); +export const photonAttachmentLocatorSchema = z + .object({ + kind: z.literal("photon_attachment"), + lineId: guid, + chatGuid: guid, + messageGuid: guid, + attachmentGuid: guid, + partIndex: z.number().int().nonnegative().max(100).optional(), + }) + .strict(); +export type PhotonAttachmentLocator = z.infer< + typeof photonAttachmentLocatorSchema +>; + +type PhotonCompanion = + | { unavailable: true } + | { unavailable: false; data: Buffer; fileName: string; mimeType: string }; +const companions = new WeakMap(); +/** Bytes are ephemeral. Recovery downloads the source-bound primary and companion again. */ +export function takePhotonCompanion(body: Buffer): PhotonCompanion | undefined { + const companion = companions.get(body); + companions.delete(body); + return companion; +} + +export function photonAttachmentLocator( + attachment: Attachment, + lineId: string, + chatGuid: string, + message: ChatMessage, +): PhotonAttachmentLocator | null { + const parsed = photonAttachmentLocatorSchema.safeParse( + attachment.fetchMetadata + ? { + kind: attachment.fetchMetadata.kind, + lineId: attachment.fetchMetadata.lineId, + chatGuid: attachment.fetchMetadata.chatGuid, + messageGuid: attachment.fetchMetadata.messageGuid, + attachmentGuid: attachment.fetchMetadata.attachmentGuid, + } + : null, + ); + if ( + !parsed.success || + parsed.data.lineId !== lineId || + parsed.data.chatGuid !== chatGuid || + parsed.data.messageGuid !== message.id + ) + return null; + const raw = message.raw as PhotonMessage; + if ( + !raw.chatGuids?.includes(chatGuid) || + !raw.content?.attachments.some( + (candidate) => candidate.guid === parsed.data.attachmentGuid, + ) + ) + return null; + return parsed.data; +} +export async function downloadPhotonAttachment( + client: GrpcAdvancedIMessage, + lineId: string, + locator: PhotonAttachmentLocator, + allocation: "dedicated" | "shared" = "dedicated", +): Promise { + photonAttachmentLocatorSchema.parse(locator); + if (locator.lineId !== lineId) + throw new Error("Photon attachment belongs to another line"); + const source = await client.messages + .get(locator.messageGuid) + .catch((error) => { + throw photonFailure(error); + }); + const attachment = source.content.attachments.find( + (candidate) => candidate.guid === locator.attachmentGuid, + ); + if ( + source.guid !== locator.messageGuid || + !source.chatGuids.includes(locator.chatGuid) || + !attachment || + attachment.isSticker || + attachment.isHidden + ) + throw new Error( + "Photon attachment does not belong to this message and chat", + ); + if ( + !Number.isSafeInteger(attachment.totalBytes) || + attachment.totalBytes < 0 || + attachment.totalBytes > MAX_ATTACHMENT_BYTES + ) + throw new Error("Attachment exceeds the configured size limit"); + const stream = client.attachments.downloadStream(locator.attachmentGuid); + let timedOut = false; + const timer = setTimeout(() => { + timedOut = true; + void stream.close(); + }, 30_000); + timer.unref(); + const chunks: Uint8Array[] = []; + let length = 0; + let header = false; + let companionInfo: CompanionInfo | undefined; + let companionUnavailable = false; + let companionStarted = false; + let companionLength = 0; + const companionChunks: Uint8Array[] = []; + try { + for await (const part of stream) { + if (part.type === "header") { + // The shared gateway rewrites message/metadata attachment IDs to opaque + // project aliases, but streams the native UUID in download headers. + // Ownership comes from the authenticated source-message lookup above + // and this exact alias-addressed RPC, never from matching filenames. + const sharedAlias = allocation === "shared" && + /^spc-att-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(locator.attachmentGuid); + const matchingSharedHeader = sharedAlias && + /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(part.info.guid) && + part.info.totalBytes === attachment.totalBytes && + part.info.mimeType === attachment.mimeType && + part.info.fileName === attachment.fileName; + if ( + header || + (part.info.guid !== locator.attachmentGuid && !matchingSharedHeader) || + part.info.isHidden || part.info.isSticker || + !Number.isSafeInteger(part.info.totalBytes) || part.info.totalBytes < 0 || + part.info.totalBytes > MAX_ATTACHMENT_BYTES + ) + throw new Error("Photon attachment metadata changed"); + header = true; + companionInfo = part.companionInfo; + companionUnavailable = Boolean( + companionInfo && + (companionInfo.kind !== "live-photo-video" || + !["video/quicktime", "video/mp4"].includes( + companionInfo.mimeType, + ) || + !Number.isSafeInteger(companionInfo.totalBytes) || + companionInfo.totalBytes <= 0 || + companionInfo.totalBytes > MAX_ATTACHMENT_BYTES), + ); + } else if (part.type === "primaryChunk") { + if (companionStarted) + throw new Error("Photon attachment chunks arrived out of order"); + if (!header) throw new Error("Photon attachment header is missing"); + length += part.data.length; + if (length > MAX_ATTACHMENT_BYTES) + throw new Error("Attachment exceeds the configured size limit"); + chunks.push(part.data); + } else if (part.type === "companionChunk") { + if (!header || !companionInfo) + throw new Error("Photon companion metadata is missing"); + companionStarted = true; + companionLength += part.data.length; + if (companionUnavailable || companionLength > MAX_ATTACHMENT_BYTES) { + companionUnavailable = true; + break; + } + companionChunks.push(part.data); + } + } + if (timedOut || !header || !length) + throw new PhotonError( + "attachment_not_ready", + "Photon attachment is still being prepared; retry download", + ); + if (attachment.totalBytes > 0 && length !== attachment.totalBytes) + throw new PhotonError( + "attachment_not_ready", + "Photon attachment transfer is incomplete", + ); + if ( + companionInfo && + !companionUnavailable && + companionLength !== companionInfo.totalBytes + ) + throw new PhotonError( + "attachment_not_ready", + "Photon Live Photo companion is still being prepared", + ); + const body = Buffer.concat(chunks); + if (companionUnavailable) companions.set(body, { unavailable: true }); + else if (companionInfo) + companions.set(body, { + unavailable: false, + data: Buffer.concat(companionChunks), + fileName: companionInfo.fileName, + mimeType: companionInfo.mimeType, + }); + return body; + } catch (error) { + if (error instanceof Error && !("code" in error)) throw error; + throw photonFailure(error); + } finally { + clearTimeout(timer); + await stream.close(); + } +} diff --git a/server/src/services/photon/cloud.ts b/server/src/services/photon/cloud.ts new file mode 100644 index 0000000000..b7cbc6c0cb --- /dev/null +++ b/server/src/services/photon/cloud.ts @@ -0,0 +1,350 @@ +import { createHash } from "node:crypto"; +import { + photonLineIdSchema, + photonProjectIdSchema, + type PhotonProjectInspection, +} from "@paperclipai/shared"; +import { IMessageError } from "@photon-ai/advanced-imessage"; + +const CLOUD_ORIGIN = "https://spectrum.photon.codes"; +const MAX_RESPONSE_BYTES = 256 * 1024; +export class PhotonError extends Error { + constructor( + readonly code: + | "credentials" + | "line_unavailable" + | "quota" + | "network" + | "history_gap" + | "delivery_unknown" + | "attachment_not_ready" + | "invalid_response" + | "rejected", + message: string, + readonly retryAfterMs?: number, + ) { + super(message); + this.name = "PhotonError"; + } +} + +/** Only documented semantic rejections prove a write did not happen. */ +export function photonFailure(error: unknown, writing = false): PhotonError { + if (error instanceof PhotonError) return error; + if (error instanceof IMessageError) { + if ( + [ + "unauthenticated", + "tokenExpired", + "tokenBlocked", + "unauthorized", + ].includes(error.code) + ) + return new PhotonError( + "credentials", + "Photon rejected the selected line credentials; reconnect the channel", + ); + if ( + [ + "dailyLimitExceeded", + "recipientLimitExceeded", + "uploadRateExceeded", + "recipientCoolingDown", + "recipientLocked", + "sendReceiveRatioExceeded", + "contentDuplicateExceeded", + ].includes(error.code) + ) + return new PhotonError( + "quota", + "Photon has temporarily limited this line", + error.retryAfter, + ); + if (error.code === "attachmentNotReady") + return new PhotonError( + "attachment_not_ready", + "Photon attachment is still being prepared", + ); + if ( + [ + "chatNotFound", + "messageNotFound", + "attachmentNotFound", + "invalidArgument", + "preconditionFailed", + "operationNotSupported", + ].includes(error.code) + ) + return new PhotonError( + "rejected", + `Photon rejected the operation (${error.code})`, + ); + } + return writing + ? new PhotonError( + "delivery_unknown", + "Photon delivery is unknown; reconcile its receipt before retrying", + ) + : new PhotonError("network", "Photon connection interrupted; retrying"); +} +/** Shared credentials own one project, not any number in the provider pool. */ +export function photonSharedIdentity(projectId: string): string { + photonProjectIdSchema.parse(projectId); + return `photon-project:${projectId}`; +} +export function photonSharedScope(projectId: string): string { + photonProjectIdSchema.parse(projectId); + return `shared-${createHash("sha256").update(projectId).digest("hex").slice(0, 48)}`; +} +interface CloudAllocation { + sharedToken?: string; + inspection: PhotonProjectInspection; + tokens: ReadonlyMap; + expiresIn: number; +} +function record(value: unknown): value is Record { + return !!value && typeof value === "object" && !Array.isArray(value); +} + +/** Only this module ever sees a Cloud project secret or minted line tokens. */ +export class PhotonCloudClient { + constructor(private readonly fetchImpl: typeof fetch = fetch) {} + private async request( + projectId: string, + projectSecret: string, + suffix: string, + method: string, + ): Promise { + photonProjectIdSchema.parse(projectId); + if (!projectSecret || projectSecret.length > 4096) + throw new PhotonError("credentials", "Enter a Photon project secret"); + let response: Response; + try { + response = await this.fetchImpl( + `${CLOUD_ORIGIN}/projects/${encodeURIComponent(projectId)}/${suffix}`, + { + method, + redirect: "error", + signal: AbortSignal.timeout(15_000), + headers: { + authorization: `Basic ${Buffer.from(`${projectId}:${projectSecret}`).toString("base64")}`, + accept: "application/json", + }, + }, + ); + } catch { + throw new PhotonError( + "network", + "Photon Cloud could not be reached; retry the connection", + ); + } + if (response.status === 401 || response.status === 403) + throw new PhotonError( + "credentials", + "Photon rejected this project ID or secret", + ); + if (response.status === 429) + throw new PhotonError( + "quota", + "Photon Cloud request limit reached; retry later", + ); + if (!response.ok) + throw new PhotonError( + "network", + `Photon Cloud returned HTTP ${response.status}`, + ); + const reader = response.body?.getReader(); + if (!reader) + throw new PhotonError( + "invalid_response", + "Photon returned an empty response", + ); + const chunks: Uint8Array[] = []; + let length = 0; + try { + for (;;) { + const next = await reader.read(); + if (next.done) break; + length += next.value.length; + if (length > MAX_RESPONSE_BYTES) + throw new PhotonError( + "invalid_response", + "Photon project response exceeds the supported size", + ); + chunks.push(next.value); + } + const body: unknown = JSON.parse(Buffer.concat(chunks).toString("utf8")); + if (!record(body) || body.succeed !== true || !record(body.data)) + throw new PhotonError( + "invalid_response", + "Photon did not return a valid project response", + ); + return body.data; + } catch (error) { + if (error instanceof PhotonError) throw error; + throw new PhotonError( + "invalid_response", + "Photon returned an invalid project response", + ); + } finally { + await reader.cancel().catch(() => {}); + } + } + async allocation( + projectId: string, + projectSecret: string, + ): Promise { + const project = await this.request(projectId, projectSecret, "", "GET"); + if ( + record(project) && + typeof project.id === "string" && + project.id !== projectId + ) + throw new PhotonError( + "credentials", + "Photon returned a different project identity", + ); + const data = await this.request( + projectId, + projectSecret, + "imessage/tokens", + "POST", + ); + if (!record(data)) + throw new PhotonError("invalid_response", "Photon allocation is missing"); + const projectName = + record(project) && typeof project.name === "string" + ? project.name.slice(0, 160) + : projectId; + const inspection: PhotonProjectInspection = { + projectId, + projectName, + allocation: data.type === "dedicated" ? "dedicated" : "shared", + eligible: false, + lines: [], + }; + if (data.type === "shared") { + if (typeof data.token !== "string" || !data.token || data.token.length > 16_384 || + typeof data.expiresIn !== "number" || !Number.isFinite(data.expiresIn) || data.expiresIn < 30) + throw new PhotonError("invalid_response", "Photon returned invalid shared project credentials"); + inspection.eligible = true; + return { inspection, sharedToken: data.token, tokens: new Map(), expiresIn: Math.min(data.expiresIn, 86_400) }; + } + if ( + data.type !== "dedicated" || + !record(data.auth) || + !record(data.numbers) || + typeof data.expiresIn !== "number" || + !Number.isFinite(data.expiresIn) || + data.expiresIn < 30 + ) { + throw new PhotonError( + "line_unavailable", + "Photon did not supply a dedicated line with a stable phone number", + ); + } + const tokens = new Map(); + for (const [lineId, token] of Object.entries(data.auth)) { + if ( + !photonLineIdSchema.safeParse(lineId).success || + typeof token !== "string" || + !token || + token.length > 16_384 + ) + continue; + const phoneNumber = data.numbers[lineId]; + if ( + typeof phoneNumber !== "string" || + !/^\+[1-9]\d{6,14}$/.test(phoneNumber) + ) + continue; + tokens.set(lineId, token); + inspection.lines.push({ lineId, phoneNumber, eligible: true }); + } + if (inspection.lines.length > 256) + throw new PhotonError( + "invalid_response", + "Photon returned too many lines", + ); + inspection.eligible = inspection.lines.length > 0; + return { inspection, tokens, expiresIn: Math.min(data.expiresIn, 86_400) }; + } + async inspect( + projectId: string, + projectSecret: string, + ): Promise { + return (await this.allocation(projectId, projectSecret)).inspection; + } +} + +/** A fixed identity; renewal can replace its token, never its project/line/number. */ +export class PhotonLineAuthentication { + private current: { token: string; renewAt: number } | undefined; + private renewing: Promise | undefined; + private retired = false; + constructor( + readonly identity: { + allocation?: "shared" | "dedicated"; + projectId: string; + lineId: string; + phoneNumber: string; + }, + private readonly secret: string, + private readonly cloud = new PhotonCloudClient(), + private readonly now = Date.now, + ) { + photonProjectIdSchema.parse(identity.projectId); + photonLineIdSchema.parse(identity.lineId); + if (identity.allocation === "shared" && (identity.lineId !== photonSharedScope(identity.projectId) || identity.phoneNumber !== photonSharedIdentity(identity.projectId))) + throw new PhotonError("credentials", "Photon shared identity must match its project"); + } + get address(): string { + return this.identity.allocation === "shared" ? "imessage.spectrum.photon.codes:443" : `${this.identity.lineId}.imsg.photon.codes:443`; + } + retire(): void { + this.retired = true; + this.current = undefined; + } + async token(): Promise { + if (this.retired) + throw new PhotonError("credentials", "Photon runtime has been retired"); + if (this.current && this.current.renewAt > this.now()) + return this.current.token; + this.renewing ??= this.renew().finally(() => { + this.renewing = undefined; + }); + return this.renewing; + } + private async renew(): Promise { + this.current = undefined; + const allocation = await this.cloud.allocation( + this.identity.projectId, + this.secret, + ); + if (allocation.inspection.allocation !== (this.identity.allocation ?? "dedicated")) + throw new PhotonError("line_unavailable", "Photon project allocation changed; create a new channel for the new identity"); + if (this.identity.allocation === "shared") { + if (!allocation.sharedToken || this.retired) + throw new PhotonError("credentials", "Photon shared credentials are unavailable"); + this.current = { token: allocation.sharedToken, renewAt: this.now() + allocation.expiresIn * 800 }; + return allocation.sharedToken; + } + const line = allocation.inspection.lines.find( + (candidate) => candidate.lineId === this.identity.lineId, + ); + const token = allocation.tokens.get(this.identity.lineId); + if ( + !line?.eligible || + line.phoneNumber !== this.identity.phoneNumber || + !token + ) + throw new PhotonError( + "line_unavailable", + "The selected Photon number is no longer available; reconnect the same line or create a new channel", + ); + if (this.retired) + throw new PhotonError("credentials", "Photon runtime has been retired"); + this.current = { token, renewAt: this.now() + allocation.expiresIn * 800 }; + return token; + } +} diff --git a/server/src/services/photon/interactions.ts b/server/src/services/photon/interactions.ts new file mode 100644 index 0000000000..e190df5f7f --- /dev/null +++ b/server/src/services/photon/interactions.ts @@ -0,0 +1,365 @@ +import { nativeSha256 } from "../native-runtime/canonical.js"; +import { createHash } from "node:crypto"; +import type { + AskUserQuestionsInteraction, + AskUserQuestionsAnswer, + IssueThreadInteraction, + RequestConfirmationInteraction, +} from "@paperclipai/shared"; +import { PhotonChatAdapter } from "./adapter.js"; +import { PhotonError, photonFailure } from "./cloud.js"; +import { projectSafeChatPublicationText } from "../chat-publication-projection.js"; + +export class PhotonAnswerValidationError extends Error {} + +export interface PhotonInteractionBinding { + version: 1; + reference: string; + interactionId: string; + publicationId: string; + sessionGeneration: number; + expiresAt: string; +} +export interface PhotonPromptReceipt { + schema: 1; + reference: string; + questionIndex: number; + publicationId: string; + promptMessageGuid: string; + promptMessageGuids?: string[]; + pollMessageGuid?: string; + options: Record; +} +export interface PhotonDraft { + schema: 1; + interactionId: string; + userId: string; + principalId: string; + answers: AskUserQuestionsAnswer[]; + decision?: "accept" | "reject"; + reason?: string; + lastSequence: number; +} +export function nativePhotonInteraction( + interaction: IssueThreadInteraction, +): interaction is AskUserQuestionsInteraction | RequestConfirmationInteraction { + if (interaction.kind === "ask_user_questions") + return ( + interaction.payload.questions.length > 0 && + interaction.payload.questions.length <= 64 + ); + return ( + interaction.kind === "request_confirmation" && + !interaction.payload.toolAction && + !interaction.payload.secretProposal && + !interaction.payload.connectionAuthorization + ); +} +export function photonResponseCommand(text: string): { + command: "answer" | "submit"; + reference: string; + questionIndex: number; + value: string; +} | null { + const match = + /^\/(answer|submit)\s+([a-zA-Z0-9_-]{8,24})(?:\.(\d{1,2}))?(?:\s+([\s\S]*))?$/i.exec( + text.trim(), + ); + if (!match) return null; + return { + command: match[1].toLowerCase() as "answer" | "submit", + reference: match[2], + questionIndex: Number(match[3] ?? 1) - 1, + value: match[4]?.trim() ?? "", + }; +} +export function parsePhotonQuestionAnswer( + interaction: AskUserQuestionsInteraction, + questionIndex: number, + value: string, +): AskUserQuestionsAnswer { + const question = interaction.payload.questions[questionIndex]; + if (!question) + throw new PhotonAnswerValidationError("That question does not exist"); + if (value.toLowerCase() === "skip") { + if (question.required !== false) + throw new PhotonAnswerValidationError("This question requires an answer"); + return { questionId: question.id, optionIds: [] }; + } + if (!value || value.length > 100_000) + throw new PhotonAnswerValidationError( + "Enter a nonempty answer within the task’s length limit", + ); + const numbers = + /^\d+(?:[ ,]+\d+)*$/.test(value) && + question.options.some((option) => !option.freeText) + ? value.split(/[ ,]+/).map(Number) + : null; + if (numbers) { + if ( + new Set(numbers).size !== numbers.length || + numbers.some((index) => index < 1 || index > question.options.length) + ) + throw new PhotonAnswerValidationError( + "Choose valid option numbers without duplicates", + ); + if (question.selectionMode === "single" && numbers.length !== 1) + throw new PhotonAnswerValidationError("Choose one option"); + if (numbers.some((index) => question.options[index - 1].freeText)) + throw new PhotonAnswerValidationError("Write your custom answer as text"); + return { + questionId: question.id, + optionIds: numbers.map((index) => question.options[index - 1].id), + }; + } + const freeText = question.options.find((option) => option.freeText); + if (question.options.length && question.allowOther === false && !freeText) + throw new PhotonAnswerValidationError( + "Answer with the option number(s) shown in the prompt", + ); + return { + questionId: question.id, + optionIds: freeText ? [freeText.id] : [], + otherText: value, + }; +} +export function photonAnswersMatch( + interaction: AskUserQuestionsInteraction, + result: unknown, +): boolean { + if (!result || typeof result !== "object") return false; + const record = result as { + code?: unknown; + interactionId?: unknown; + answersSha256?: unknown; + }; + return ( + record.code === "photon_question_answered" && + record.interactionId === interaction.id && + record.answersSha256 === nativeSha256(interaction.result?.answers) + ); +} + +/** Prompt and poll receipts persist independently, before the outbox is settled. + * A vote arriving before this binding is finalized must wait for this record. */ +export async function publishPhotonPrompt(input: { + adapter: PhotonChatAdapter; + threadId: string; + binding: PhotonInteractionBinding; + interaction: AskUserQuestionsInteraction | RequestConfirmationInteraction; + questionIndex: number; + taskUrl?: string | null; + retryUnknown?: boolean; + assertCurrent(): Promise; +}): Promise { + const { adapter, binding, interaction, questionIndex, assertCurrent } = input; + const key = `prompt:${binding.reference}:${questionIndex}`; + const digest = nativeSha256({ + binding, + threadId: input.threadId, + kind: interaction.kind, + payload: interaction.payload, + questionIndex, + taskUrl: input.taskUrl ?? null, + }); + await adapter.state.update<{ digest: string }>( + `${key}:identity`, + (current) => { + if (current && current.digest !== digest) + throw new PhotonError( + "rejected", + "Photon prompt changed after preparation", + ); + return current ?? { digest }; + }, + ); + const existing = await adapter.state.read(key); + if (existing) { + for (const guid of existing.promptMessageGuids ?? [ + existing.promptMessageGuid, + ]) + await adapter.state.update( + `prompt-message:${guid}`, + (current) => current ?? existing, + ); + if (existing.pollMessageGuid) + await adapter.state.update( + `poll-message:${existing.pollMessageGuid}`, + (current) => current ?? existing, + ); + return existing; + } + const question = + interaction.kind === "ask_user_questions" + ? interaction.payload.questions[questionIndex] + : null; + if (interaction.kind === "ask_user_questions" && !question) + throw new PhotonAnswerValidationError("Photon question index is invalid"); + const reference = `${binding.reference}.${questionIndex + 1}`; + const title = + projectSafeChatPublicationText( + question?.prompt ?? + (interaction as RequestConfirmationInteraction).payload.prompt, + ).trim() || "Input needed"; + const choices = question + ? question.options.map((option) => ({ + id: option.id, + label: + projectSafeChatPublicationText(option.label).trim() || + `Choice ${question.options.indexOf(option) + 1}`, + })) + : [ + { + id: "accept", + label: projectSafeChatPublicationText( + (interaction as RequestConfirmationInteraction).payload + .acceptLabel ?? "Accept", + ), + }, + { + id: "reject", + label: projectSafeChatPublicationText( + (interaction as RequestConfirmationInteraction).payload + .rejectLabel ?? "Reject", + ), + }, + ]; + const nativePoll = + (!question || + (question.selectionMode === "single" && + !question.allowOther && + !question.options.some((o) => o.freeText))) && + choices.length >= 2 && + choices.length <= 10; + const text = [ + title, + interaction.kind === "request_confirmation" && + interaction.payload.detailsMarkdown + ? projectSafeChatPublicationText(interaction.payload.detailsMarkdown) + : "", + question?.helpText ? projectSafeChatPublicationText(question.helpText) : "", + ...choices.map((choice, index) => `${index + 1}. ${choice.label}`), + `Reply to this message, or send /answer ${reference} ${question?.selectionMode === "multi" ? "" : ""}.`, + question?.required === false ? `Optional: /answer ${reference} skip` : "", + interaction.kind === "request_confirmation" && + interaction.payload.rejectRequiresReason + ? "To reject, send Reject followed by your reason." + : "", + interaction.kind === "ask_user_questions" && + interaction.payload.questions.length > 1 + ? `Answers are saved for you. Finish with /submit ${binding.reference}.` + : "", + input.taskUrl ? `Open this Paperclip task: ${input.taskUrl}` : "", + ] + .filter(Boolean) + .join("\n\n"); + const prompt = await adapter.publish( + input.threadId, + `${binding.publicationId}:question:${questionIndex}`, + { markdown: text }, + { assertCurrent, retryUnknown: input.retryUnknown }, + ); + const receipt: PhotonPromptReceipt = { + schema: 1, + reference: binding.reference, + publicationId: binding.publicationId, + questionIndex, + promptMessageGuid: prompt.id, + promptMessageGuids: prompt.messageIds, + options: {}, + }; + if (nativePoll) { + const stateKey = `poll:${binding.publicationId}:${questionIndex}`; + type PollSend = { + schema: 1; + phase: "prepared" | "creating" | "created"; + receipt?: { + pollMessageGuid: string; + options: Array<{ optionIdentifier: string; text: string }>; + }; + }; + let saved = await adapter.state.read(stateKey); + if (saved?.phase === "creating" && !input.retryUnknown) + throw new PhotonError( + "delivery_unknown", + "Photon poll creation is unknown; reconcile this publication before retrying", + ); + if (!saved || saved.phase !== "created") { + await assertCurrent(); + await adapter.state.update(stateKey, (current) => { + if (current?.phase === "creating" && !input.retryUnknown) + throw new PhotonAnswerValidationError("Photon poll already claimed"); + return { schema: 1, phase: "creating" }; + }); + const poll = await adapter.client.polls + .create( + adapter.decodeThreadId(input.threadId).chatGuid, + title, + choices.map((choice) => choice.label), + { + clientMessageId: createHash("sha256") + .update(`${adapter.state.scope.endpointId}:${stateKey}`) + .digest("hex"), + }, + ) + .catch(async (error) => { + const failure = photonFailure(error, true); + if (failure.code !== "delivery_unknown") + await adapter.state.update(stateKey, () => ({ + schema: 1, + phase: "prepared", + })); + throw failure; + }); + if ( + !poll.pollMessageGuid || + poll.options.length !== choices.length || + new Set(poll.options.map((option) => option.optionIdentifier)).size !== + choices.length || + poll.options.some( + (option, index) => + !option.optionIdentifier || + option.text.trim() !== choices[index].label.trim(), + ) + ) + throw new PhotonError( + "delivery_unknown", + "Photon returned an incomplete poll receipt", + ); + saved = await adapter.state.update(stateKey, () => ({ + schema: 1, + phase: "created", + receipt: { + pollMessageGuid: poll.pollMessageGuid, + options: poll.options.map((option) => ({ + optionIdentifier: option.optionIdentifier, + text: option.text, + })), + }, + })); + } + receipt.pollMessageGuid = saved.receipt!.pollMessageGuid; + // Creation response preserves requested option order; labels/titles are never a lookup key. + receipt.options = Object.fromEntries( + saved.receipt!.options.map((option, index) => [ + option.optionIdentifier, + choices[index].id, + ]), + ); + } + await adapter.state.update( + key, + (current) => current ?? receipt, + ); + for (const guid of prompt.messageIds) + await adapter.state.update( + `prompt-message:${guid}`, + (current) => current ?? receipt, + ); + if (receipt.pollMessageGuid) + await adapter.state.update( + `poll-message:${receipt.pollMessageGuid}`, + (current) => current ?? receipt, + ); + return receipt; +} diff --git a/server/src/services/photon/media.ts b/server/src/services/photon/media.ts new file mode 100644 index 0000000000..797c3d83ca --- /dev/null +++ b/server/src/services/photon/media.ts @@ -0,0 +1,140 @@ +import { spawn } from "node:child_process"; +import { createRequire } from "node:module"; +import sharp from "sharp"; +import { MAX_ATTACHMENT_BYTES } from "../../attachment-types.js"; + +const require = createRequire(import.meta.url); +const MAX_PIXELS = 50_000_000; +export const HEIF_CONTENT_TYPES = new Set([ + "image/heic", + "image/heif", + "image/heic-sequence", + "image/heif-sequence", +]); + +/** Validate bounded ISO-BMFF structure before invoking any native decoder. */ +export function validateHeifDimensions(body: Buffer): void { + let boxes = 0; + let dimensions = 0; + let totalPixels = 0; + let branded = false; + const visit = (start: number, end: number, depth: number) => { + if (depth > 8) throw new Error("HEIF metadata nesting is too deep"); + for (let at = start; at < end; ) { + if (++boxes > 4096 || end - at < 8) + throw new Error("Invalid HEIF box structure"); + let size = body.readUInt32BE(at); + const type = body.toString("ascii", at + 4, at + 8); + let header = 8; + if (size === 1) { + if (end - at < 16) throw new Error("Invalid HEIF box length"); + const extended = body.readBigUInt64BE(at + 8); + if (extended > BigInt(body.length)) + throw new Error("HEIF box exceeds file bounds"); + size = Number(extended); + header = 16; + } else if (size === 0) size = end - at; + if (size < header || at + size > end) + throw new Error("HEIF box exceeds file bounds"); + const content = at + header; + if (type === "ftyp") { + if (size < header + 8) throw new Error("HEIF file type is missing"); + const brands = body.toString("ascii", content, at + size); + branded = /heic|heix|hevc|hevx|mif1|msf1/.test(brands); + } else if (type === "ispe") { + if (size !== header + 12) + throw new Error("Invalid HEIF image dimensions"); + const width = body.readUInt32BE(content + 4); + const height = body.readUInt32BE(content + 8); + if ( + !width || + !height || + width > 16_384 || + height > 16_384 || + width * height > MAX_PIXELS + ) + throw new Error("HEIF decoded image exceeds the pixel limit"); + totalPixels += width * height; + if (totalPixels > MAX_PIXELS * 3 || ++dimensions > 512) + throw new Error("HEIF image collection exceeds the pixel limit"); + } else if (["meta", "iprp", "ipco"].includes(type)) { + visit(content + (type === "meta" ? 4 : 0), at + size, depth + 1); + } + at += size; + } + }; + if (!body.length || body.length > MAX_ATTACHMENT_BYTES) + throw new Error("HEIF exceeds the attachment byte limit"); + visit(0, body.length, 0); + if (!branded || !dimensions) + throw new Error("HEIF dimensions could not be verified"); +} + +export async function validatePhotonImage( + body: Buffer, + contentType: string, +): Promise { + if (!contentType.startsWith("image/")) return; + if (HEIF_CONTENT_TYPES.has(contentType)) return validateHeifDimensions(body); + const metadata = await sharp(body, { + limitInputPixels: MAX_PIXELS, + failOn: "error", + }).metadata(); + if ( + !metadata.width || + !metadata.height || + metadata.width * metadata.height * (metadata.pages ?? 1) > MAX_PIXELS + ) + throw new Error("Decoded image exceeds the pixel limit"); + const formats: Record = { + "image/jpeg": ["jpeg"], + "image/jpg": ["jpeg"], + "image/png": ["png"], + "image/webp": ["webp"], + "image/gif": ["gif"], + }; + if (!formats[contentType]?.includes(metadata.format ?? "")) + throw new Error("Image bytes do not match the declared content type"); +} + +/** Isolate the native converter with a deadline and bounded input/output. + * Native packages exist for macOS, Windows and Linux glibc x64/arm64. + * Unsupported hosts retain the original and report an unavailable preview. */ +export async function photonHeifPreview(body: Buffer): Promise { + validateHeifDimensions(body); + const modulePath = require.resolve("heif2jpeg"); + const script = `const {heifToJpeg}=require(process.argv[1]);const chunks=[];process.stdin.on('data',c=>chunks.push(c));process.stdin.on('end',async()=>{try{const jpeg=await heifToJpeg(Buffer.concat(chunks),{quality:80});process.stdout.end(jpeg);}catch{process.exitCode=1;}});`; + const jpeg = await new Promise((resolve, reject) => { + const child = spawn( + process.execPath, + ["--max-old-space-size=256", "--eval", script, modulePath], + { stdio: ["pipe", "pipe", "ignore"], windowsHide: true }, + ); + let length = 0; + const chunks: Buffer[] = []; + const timer = setTimeout(() => { + child.kill("SIGKILL"); + reject(new Error("HEIF preview conversion timed out")); + }, 10_000); + child.stdout.on("data", (chunk: Buffer) => { + length += chunk.length; + if (length > MAX_ATTACHMENT_BYTES) { + child.kill("SIGKILL"); + reject(new Error("HEIF preview exceeds the attachment byte limit")); + } else chunks.push(chunk); + }); + child.on("error", () => { + clearTimeout(timer); + reject(new Error("HEIF preview converter is unavailable")); + }); + child.on("close", (code) => { + clearTimeout(timer); + if (code === 0 && length > 0) resolve(Buffer.concat(chunks)); + else reject(new Error("HEIF preview conversion failed on this host")); + }); + child.stdin.on("error", () => {}); + child.stdin.end(body); + }); + await validatePhotonImage(jpeg, "image/jpeg"); + return jpeg; +} diff --git a/server/src/services/photon/receiver.ts b/server/src/services/photon/receiver.ts new file mode 100644 index 0000000000..c7629b95b0 --- /dev/null +++ b/server/src/services/photon/receiver.ts @@ -0,0 +1,217 @@ +import type { PhotonRecoveryEvent } from "./recovery-transport.js"; +import type { + GrpcAdvancedIMessage, + LiveEvent, + TypedEventStream, +} from "@photon-ai/advanced-imessage"; +import { PhotonError } from "./cloud.js"; +import { PhotonState } from "./state.js"; + +interface Checkpoint { + schema: 1; + lineId: string; + sequence: number; +} +export interface PhotonReceiverOptions { + client: GrpcAdvancedIMessage; + state: PhotonState; + lineId: string; + intakeAfter: number; + allocation?: "dedicated" | "shared"; + catchUp?(sequence?: number): TypedEventStream; + /** Renewal verifies both the selected identity and the current endpoint lease. */ + assertOwned(): Promise; + admit(event: LiveEvent): Promise; + failure(error: unknown): Promise; + /** Production persists the cursor under the same transaction as its lease fence. */ + commitCheckpoint?(sequence: number): Promise; +} + +/** Live streams wake a serialized catch-up reader. Only that complete event log + * advances the checkpoint, so cross-stream ordering can never skip an event. */ +export class PhotonReceiver { + private stopped = false; + private streams: Array> = []; + private running?: Promise; + private requested = false; + private catchUpStream?: TypedEventStream; + private timer?: ReturnType; + constructor(private readonly options: PhotonReceiverOptions) {} + start(): void { + if (this.stopped || this.timer) return; + const { client } = this.options; + this.streams = [ + client.messages.subscribeEvents(), + client.chats.subscribeEvents(), + ...(this.options.allocation === "shared" ? [] : [client.groups.subscribeEvents()]), + client.polls.subscribeEvents(), + ]; + for (const stream of this.streams) { + void (async () => { + try { + for await (const _event of stream) { + if (this.stopped) break; + this.request(); + } + if (!this.stopped) + await this.options.failure( + new PhotonError("network", "Photon event stream disconnected"), + ); + } catch (error) { + if (!this.stopped) await this.options.failure(error); + } + })().catch(() => {}); + } + this.timer = setInterval(() => this.request(), 15_000); + this.timer.unref(); + this.request(); + } + private request(): void { + if (this.stopped) return; + this.requested = true; + if (this.running) return; + this.running = (async () => { + while (this.requested && !this.stopped) { + this.requested = false; + await this.catchUp(); + } + })() + .catch(async (error) => { + if (!this.stopped) await this.options.failure(error); + }) + .finally(() => { + this.running = undefined; + }); + } + /** Exposed for deterministic recovery tests, never an external route. */ + async catchUp(): Promise { + const { state, lineId, client, assertOwned, admit, intakeAfter } = + this.options; + await assertOwned(); + const original = await state.read("checkpoint"); + if (!original && (await state.read("receiver-initialized"))) + throw new PhotonError( + "history_gap", + "Photon receiver checkpoint is missing; operator recovery is required", + ); + if ( + original && + (original.schema !== 1 || + original.lineId !== lineId || + !Number.isSafeInteger(original.sequence) || + original.sequence < 0) + ) + throw new PhotonError( + "history_gap", + "Photon checkpoint is invalid; operator recovery is required", + ); + const shared = this.options.allocation === "shared"; + let sequence = original?.sequence; + let batchEvents = 0; + const stream = + this.options.catchUp?.(sequence) ?? client.events.catchUp(sequence); + this.catchUpStream = stream; + let completed = false; + try { + for await (const event of stream) { + if (this.stopped) return; + await assertOwned(); + if (event.type === "catchup.complete") { + if ( + !Number.isSafeInteger(event.headSequence) || + event.headSequence < 0 || + (sequence !== undefined && (shared ? event.headSequence < sequence : event.headSequence !== sequence)) + ) + throw new PhotonError( + "history_gap", + "Photon history has a gap or reset; operator recovery is required", + ); + // Shared gateway replay is project-filtered: sequence numbers are + // increasing but not adjacent (the first live project event may be + // > 1 billion). A complete replay barrier covers the filtered tail. + // Commit shared batches only here, after every admission succeeds; + // malformed ordering or interrupted replay keeps the previous cursor. + sequence = shared ? event.headSequence : sequence ?? event.headSequence; + await this.checkpoint(sequence); + completed = true; + break; + } + if (!Number.isSafeInteger(event.sequence) || event.sequence < 1) + throw new PhotonError( + "history_gap", + "Photon returned an invalid event sequence", + ); + if (++batchEvents > 100_000) + throw new PhotonError("history_gap", "Photon replay exceeds the supported recovery window"); + if (shared && sequence !== undefined && event.sequence < sequence && event.sequence > (original?.sequence ?? -1)) + throw new PhotonError("history_gap", "Photon replay arrived out of order; the saved cursor was retained"); + if (sequence !== undefined && event.sequence <= sequence) continue; + if (!shared && sequence !== undefined && event.sequence !== sequence + 1) + throw new PhotonError( + "history_gap", + "Photon event history is incomplete; operator recovery is required", + ); + // On the first connection, the server may retain only a tail of history. + // Establish that boundary explicitly; it is never allowed after a cursor. + sequence ??= event.sequence - 1; + if (event.type !== "photon.ignored") { + const occurredAt = new Date(event.occurredAt).getTime(); + if (!Number.isFinite(occurredAt)) + throw new PhotonError( + "invalid_response", + "Photon event timestamp is invalid", + ); + if (occurredAt >= intakeAfter) await admit(event); + } + // admission must durably store or classify even irrelevant events. + if (!shared) await this.checkpoint(event.sequence); + sequence = event.sequence; + } + if (!completed && !this.stopped) + throw new PhotonError( + "network", + "Photon catch-up ended before its checkpoint barrier", + ); + } finally { + await stream.close(); + if (this.catchUpStream === stream) this.catchUpStream = undefined; + } + } + private async checkpoint(sequence: number): Promise { + await this.options.assertOwned(); + if (this.options.commitCheckpoint) + return this.options.commitCheckpoint(sequence); + await writePhotonCheckpoint( + this.options.state, + this.options.lineId, + sequence, + ); + } + + async close(): Promise { + this.stopped = true; + if (this.timer) clearInterval(this.timer); + await this.catchUpStream?.close(); + await Promise.allSettled(this.streams.map((stream) => stream.close())); + // client.close interrupts a blocked catch-up RPC during runtime shutdown. + } +} + +export async function writePhotonCheckpoint( + state: PhotonState, + lineId: string, + sequence: number, +): Promise { + await state.update("checkpoint", (current) => { + if (current && (current.lineId !== lineId || current.sequence > sequence)) + throw new PhotonError( + "history_gap", + "Photon checkpoint ownership changed", + ); + return { schema: 1, lineId, sequence }; + }); + await state.update( + "receiver-initialized", + (current) => current ?? { schema: 1, lineId }, + ); +} diff --git a/server/src/services/photon/recovery-transport.ts b/server/src/services/photon/recovery-transport.ts new file mode 100644 index 0000000000..4692cb7a59 --- /dev/null +++ b/server/src/services/photon/recovery-transport.ts @@ -0,0 +1,178 @@ +import { Client, credentials, Metadata, status } from "@grpc/grpc-js"; +import { + decodeCatchUpEvent, + TypedEventStream, + type CatchUpEvent, +} from "@photon-ai/advanced-imessage"; +import { + PhotonError, + photonFailure, + type PhotonLineAuthentication, +} from "./cloud.js"; +import { MAX_ATTACHMENT_BYTES } from "../../attachment-types.js"; + +export type PhotonRecoveryEvent = + | CatchUpEvent + | { type: "photon.ignored"; sequence: number }; +export const PHOTON_CATCHUP_PATH = + "/photon.imessage.v1.EventService/CatchUpEvents"; + +/** Pinned v2.1.0 protobuf envelope. The SDK decoder owns the event graph; this + * reader retains sequence-only/new-variant frames that its public API drops. */ +export function photonEnvelopeSequence(bytes: Uint8Array): number | undefined { + let offset = 0; + const integer = () => { + let value = 0n; + for (let shift = 0n; shift < 70n; shift += 7n) { + if (offset >= bytes.length) + throw new PhotonError( + "invalid_response", + "Truncated Photon recovery frame", + ); + const byte = bytes[offset++]; + value |= BigInt(byte & 127) << shift; + if (!(byte & 128)) { + if (value > BigInt(Number.MAX_SAFE_INTEGER)) + throw new PhotonError( + "history_gap", + "Photon recovery sequence exceeds the supported range", + ); + return Number(value); + } + } + throw new PhotonError( + "invalid_response", + "Invalid Photon recovery integer", + ); + }; + let sequence: number | undefined; + while (offset < bytes.length) { + const tag = integer(); + if (tag === 8) { + if (sequence !== undefined) + throw new PhotonError( + "invalid_response", + "Duplicate Photon sequence field", + ); + sequence = integer(); + continue; + } + const wire = tag & 7; + if (wire === 0) integer(); + else if (wire === 1) offset += 8; + else if (wire === 2) { + const length = integer(); + offset += length; + } else if (wire === 5) offset += 4; + else + throw new PhotonError( + "invalid_response", + "Unsupported Photon recovery frame", + ); + if (offset > bytes.length) + throw new PhotonError( + "invalid_response", + "Photon recovery field exceeds frame bounds", + ); + } + return sequence; +} +export function photonCatchUpRequest(sequence?: number): Buffer { + if (sequence === undefined) return Buffer.alloc(0); + if (!Number.isSafeInteger(sequence) || sequence < 0) + throw new PhotonError("history_gap", "Invalid Photon recovery cursor"); + let remaining = BigInt(sequence); + const bytes = [8]; + do { + const byte = Number(remaining & 127n); + remaining >>= 7n; + bytes.push(byte | (remaining ? 128 : 0)); + } while (remaining); + return Buffer.from(bytes); +} +export function decodePhotonRecoveryFrame( + bytes: Buffer, +): PhotonRecoveryEvent | null { + const sequence = photonEnvelopeSequence(bytes); + let event: CatchUpEvent | undefined; + try { + event = decodeCatchUpEvent(bytes); + } catch { + throw new PhotonError( + "invalid_response", + "Photon recovery event could not be decoded", + ); + } + return ( + event ?? + (sequence !== undefined ? { type: "photon.ignored", sequence } : null) + ); +} + +export class PhotonRecoveryTransport { + private readonly client: Client; + constructor( + private readonly authentication: PhotonLineAuthentication, + client?: Client, + ) { + this.client = + client ?? + new Client(authentication.address, credentials.createSsl(), { + "grpc.max_receive_message_length": MAX_ATTACHMENT_BYTES + 1024 * 1024, + }); + } + catchUp(sequence?: number): TypedEventStream { + const controller = new AbortController(); + const { client, authentication } = this; + async function* receive() { + const metadata = new Metadata(); + metadata.set("authorization", `Bearer ${await authentication.token()}`); + if (controller.signal.aborted) return; + const call = client.makeServerStreamRequest( + PHOTON_CATCHUP_PATH, + photonCatchUpRequest, + (bytes: Buffer) => bytes, + sequence, + metadata, + { deadline: Date.now() + 60_000 }, + ); + const abort = () => call.cancel(); + controller.signal.addEventListener("abort", abort, { once: true }); + try { + for await (const bytes of call) { + const frame = decodePhotonRecoveryFrame(bytes as Buffer); + if (frame) yield frame; + } + } catch (error) { + if (controller.signal.aborted) return; + const code = (error as { code?: number }).code; + if (code === status.OUT_OF_RANGE || code === status.FAILED_PRECONDITION) + throw new PhotonError( + "history_gap", + "Photon cannot recover the saved cursor; reconnect after reviewing the history gap", + ); + if ( + code === status.UNAUTHENTICATED || + code === status.PERMISSION_DENIED + ) + throw new PhotonError( + "credentials", + "Photon rejected the selected line credentials; reconnect the channel", + ); + if (code === status.RESOURCE_EXHAUSTED) + throw new PhotonError( + "quota", + "Photon recovery is temporarily rate limited", + ); + throw photonFailure(error); + } finally { + controller.signal.removeEventListener("abort", abort); + call.cancel(); + } + } + return new TypedEventStream(receive(), async () => controller.abort()); + } + close(): void { + this.client.close(); + } +} diff --git a/server/src/services/photon/state.ts b/server/src/services/photon/state.ts new file mode 100644 index 0000000000..2cb85ac3f4 --- /dev/null +++ b/server/src/services/photon/state.ts @@ -0,0 +1,39 @@ +import { createHash } from "node:crypto"; +import type { + ChatSdkStatePersistence, + ChatSdkStateScope, +} from "../chat-sdk-state.js"; + +/** Typed provider records share the existing company/endpoint scoped CAS store. */ +export class PhotonState { + constructor( + readonly scope: ChatSdkStateScope, + private readonly persistence: ChatSdkStatePersistence, + ) {} + private key(key: string): string { + return `photon:${createHash("sha256").update(key).digest("hex")}`; + } + async read(key: string): Promise { + const row = await this.persistence.read(this.scope, this.key(key)); + return row ? (row.value as T) : null; + } + async update(key: string, update: (current: T | null) => T): Promise { + for (let attempt = 0; attempt < 32; attempt++) { + const row = await this.persistence.read(this.scope, this.key(key)); + const value = update(row ? (row.value as T) : null); + if (Buffer.byteLength(JSON.stringify(value)) > 512 * 1024) + throw new Error("Photon state record is too large"); + if ( + await this.persistence.compareAndSet({ + ...this.scope, + key: this.key(key), + expectedVersion: row?.version ?? null, + expiresAt: null, + value, + }) + ) + return value; + } + throw new Error("Photon state changed concurrently; retry"); + } +} diff --git a/server/src/services/plugin-loader.ts b/server/src/services/plugin-loader.ts index 52d7843031..a72e5db2ef 100644 --- a/server/src/services/plugin-loader.ts +++ b/server/src/services/plugin-loader.ts @@ -793,9 +793,11 @@ function buildStandaloneBundledPluginInstallArgs( packageRoot: string, ): string[] { const packageLockfilePath = path.join(packageRoot, "pnpm-lock.yaml"); - return existsSync(packageLockfilePath) - ? ["install", "--ignore-workspace", "--frozen-lockfile"] - : ["install", "--ignore-workspace", "--no-lockfile"]; + // Never let plugin-supplied workspace settings broaden dependency resolution + // or script execution. When a plugin declares a local install policy, disable + // dependency lifecycle scripts instead of loading that workspace configuration. + const scriptArgs = existsSync(path.join(packageRoot, "pnpm-workspace.yaml")) ? ["--ignore-scripts"] : []; + return ["install", "--ignore-workspace", ...scriptArgs, existsSync(packageLockfilePath) ? "--frozen-lockfile" : "--no-lockfile"]; } function buildStandaloneBundledPluginInstallCommand( diff --git a/server/src/services/recovery/service.ts b/server/src/services/recovery/service.ts index d4ad5991f1..648a96761c 100644 --- a/server/src/services/recovery/service.ts +++ b/server/src/services/recovery/service.ts @@ -2495,7 +2495,9 @@ export function recoveryService( ? "Board operator: repair the project workspace repository URL or clone access, or configure a local checkout cwd, then explicitly retry or reassign." : "Board operator: repair the source task workspace link, project workspace cwd, or git checkout, then explicitly retry or reassign." : recoveryCause === "configuration_incomplete" - ? readConfigurationIncompletePayload(input.latestRun) + ? readConfigurationIncompletePayload(input.latestRun)?.reason === "ai_connection_unavailable" + ? "Reconnect the selected AI account or choose an available connection, then continue the task." + : readConfigurationIncompletePayload(input.latestRun) ?.reason === SANDBOX_PROVIDER_PLUGIN_NOT_READY_REASON ? `Board operator: the sandbox provider plugin named in the run failure is not ready; ${sandboxProviderPluginRemedy( readNonEmptyString( diff --git a/server/src/services/recovery/stranded-notice.test.ts b/server/src/services/recovery/stranded-notice.test.ts index 66aebf5755..b561593e72 100644 --- a/server/src/services/recovery/stranded-notice.test.ts +++ b/server/src/services/recovery/stranded-notice.test.ts @@ -241,3 +241,12 @@ describe("buildStrandedRecoveryEscalationNotice", () => { ).toBe(false); }); }); + + +it("names the unavailable AI account instead of suggesting secret bindings", () => { + const notice = buildConfigurationIncompleteRecoveryNoticeSeed({ reason: "ai_connection_unavailable", provider: "openai" }); + expect(notice.nextAction).toContain("Reconnect the selected AI account"); + expect(notice.title).toBe("AI connection needs attention"); + expect(notice.body).toContain("Reconnect the account"); + expect(notice.body).not.toContain("secret/env"); +}); diff --git a/server/src/services/recovery/stranded-notice.ts b/server/src/services/recovery/stranded-notice.ts index 4b3bc4ed49..acd1f64243 100644 --- a/server/src/services/recovery/stranded-notice.ts +++ b/server/src/services/recovery/stranded-notice.ts @@ -15,6 +15,7 @@ export type StrandedRecoveryNoticeSeed = { body: string; title: string; tone: IssueCommentPresentation["tone"]; + nextAction?: string; }; export type StrandedRecoveryEscalationNotice = { @@ -104,6 +105,14 @@ export function sandboxProviderPluginRemedy(pluginStatus: string): string { export function buildConfigurationIncompleteRecoveryNoticeSeed( configurationIncomplete?: Record | null, ): StrandedRecoveryNoticeSeed { + if (readNonEmptyStringField(configurationIncomplete, "reason") === "ai_connection_unavailable") { + return { + title: "AI connection needs attention", + body: "This task paused because its selected AI account is unavailable. Reconnect the account or choose an available connection to continue.", + nextAction: "Reconnect the selected AI account or choose an available connection, then continue the task.", + tone: "danger", + }; + } if (readNonEmptyStringField(configurationIncomplete, "reason") === SANDBOX_PROVIDER_PLUGIN_NOT_READY_REASON) { const pluginKey = readNonEmptyStringField(configurationIncomplete, "pluginKey") ?? "the sandbox provider plugin"; const pluginStatus = readNonEmptyStringField(configurationIncomplete, "pluginStatus") ?? "not ready"; @@ -184,9 +193,9 @@ export function buildStrandedRecoveryEscalationNotice(input: { ), keyValueRow( "Next action", - input.recoveryOwner + input.seed?.nextAction ?? (input.recoveryOwner ? "The recovery owner should either restore a live execution path or record the manual resolution on the source issue" - : "Inspect the evidence, then retry the original owner, explicitly reassign, repair the execution path, or record an intentional resolution", + : "Inspect the evidence, then retry the original owner, explicitly reassign, repair the execution path, or record an intentional resolution"), ), ]; diff --git a/server/src/services/run-identity.ts b/server/src/services/run-identity.ts index 117e4744b1..2bfe88ad82 100644 --- a/server/src/services/run-identity.ts +++ b/server/src/services/run-identity.ts @@ -1,5 +1,6 @@ import { and, asc, desc, eq, inArray, sql } from "drizzle-orm"; import { + agentWakeupRequests, heartbeatRuns, heartbeatRunEvents, issueComments, @@ -10,6 +11,48 @@ import { } from "@paperclipai/db"; import { conflict, forbidden } from "../errors.js"; import { isUuidLike } from "@paperclipai/shared"; +import { queuedCommentIdsFromRunContext, queuedCommentIdsFromWakePayload } from "./issue-queued-comment-queue.js"; + +/** Resolve an explicit click from persisted receipts, never caller context or message authors. */ +export async function explicitOperatorRunIdentity( + executor: Pick, + run: Pick, +) { + const [request] = run.wakeupRequestId ? await executor.select().from(agentWakeupRequests).where(and( + eq(agentWakeupRequests.id, run.wakeupRequestId), eq(agentWakeupRequests.companyId, run.companyId), + eq(agentWakeupRequests.agentId, run.agentId), eq(agentWakeupRequests.runId, run.id), + )) : []; + if (request?.payload?.manualUserWake === true) { + if (request.requestedByActorType !== "user" || !request.requestedByActorId) { + throw forbidden("Manual wake requires an authenticated user"); + } + return { actorId: request.requestedByActorId, cause: "manual_user_wake" }; + } + const prefix = "queued-comment-interrupt:"; + if (!request?.idempotencyKey?.startsWith(prefix)) return null; + const queueId = request.idempotencyKey.slice(prefix.length); + // The key only locates a candidate. The consumed queue, actor, run, company, + // agent, task, and delivered messages must all independently agree. + if (!isUuidLike(queueId)) { + throw forbidden("Queued-message interrupt authority is unavailable"); + } + const [receipt] = await executor.select().from(agentWakeupRequests).where(and( + eq(agentWakeupRequests.id, queueId), eq(agentWakeupRequests.companyId, run.companyId), + eq(agentWakeupRequests.agentId, run.agentId), eq(agentWakeupRequests.runId, run.id), + eq(agentWakeupRequests.status, "coalesced"), + )); + const marker = receipt?.payload?.queuedCommentInterrupt; + const actorId = marker && typeof marker === "object" && "actorId" in marker ? marker.actorId : null; + const ids = queuedCommentIdsFromWakePayload(receipt?.payload); + const deliveredIds = queuedCommentIdsFromRunContext(run.contextSnapshot); + if (typeof actorId !== "string" || !actorId || !ids.length || + receipt?.payload?.issueId !== run.contextSnapshot?.issueId || + !ids.every(id => deliveredIds.includes(id)) || + request.requestedByActorType !== "user" || request.requestedByActorId !== actorId) { + throw forbidden("Queued-message interrupt authority is unavailable"); + } + return { actorId, cause: "queued_comment_interrupt" }; +} export type RunIdentityContext = typeof runIdentityContexts.$inferSelect; type Executor = Pick; @@ -147,6 +190,7 @@ export async function initializeRunIdentity( .where(eq(runIdentityContexts.id, run.activeIdentityContextId)); return current!; } + const operatorIdentity = await explicitOperatorRunIdentity(tx, run); const [parent] = input.parentRunId ? await tx .select() @@ -171,10 +215,9 @@ export async function initializeRunIdentity( ), ) : []; - const parentId = - interaction?.sourceIdentityContextId ?? - input.parentContextId ?? - parent?.activeIdentityContextId; + const parentId = operatorIdentity ? null : ( + interaction?.sourceIdentityContextId ?? input.parentContextId ?? parent?.activeIdentityContextId + ); const [origin] = parentId ? await tx .select() @@ -192,12 +235,10 @@ export async function initializeRunIdentity( let current = await append(tx, { companyId: input.companyId, runId: input.runId, - responsibleUserId: origin - ? origin.responsibleUserId - : input.responsibleUserId, + responsibleUserId: operatorIdentity?.actorId ?? (origin ? origin.responsibleUserId : input.responsibleUserId), parentContextId: origin?.id ?? null, cause: - origin?.cause === "company_default" ? "company_default" : input.cause, + operatorIdentity ? operatorIdentity.cause : origin?.cause === "company_default" ? "company_default" : input.cause, correlationId: "dispatch", }); const ids = [...new Set(input.messageIds ?? [])]; @@ -220,10 +261,10 @@ export async function initializeRunIdentity( current = await append(tx, { companyId: input.companyId, runId: input.runId, - responsibleUserId: comment.authorUserId, + responsibleUserId: operatorIdentity?.actorId ?? comment.authorUserId, messageId: id, parentContextId: current.id, - cause: "instruction", + cause: operatorIdentity?.cause ?? "instruction", correlationId: `message:${id}`, }); } diff --git a/server/src/services/setup-token-session.ts b/server/src/services/setup-token-session.ts index 0a922f5a03..bf324479e4 100644 --- a/server/src/services/setup-token-session.ts +++ b/server/src/services/setup-token-session.ts @@ -109,6 +109,7 @@ export const SETUP_TOKEN_CANCELLABLE_STATES: readonly SetupTokenSessionState[] = * per environment. */ export interface SetupTokenSessionScope { + aiConnection?: import("@paperclipai/shared").AiConnectionLoginIntent; companyId: string; ownerUserId: string; // The adapter of the login. It is part of the session identity. @@ -218,6 +219,7 @@ export interface SetupTokenLeaseManager { * column on the row. */ export interface SetupTokenCleanupRecord { + aiConnection?: import("@paperclipai/shared").AiConnectionLoginIntent; sessionId: string; companyId: string; ownerUserId: string; @@ -684,6 +686,7 @@ export interface SetupTokenPromptView { * response returns the login URL through the confidential transport guard. */ export interface SetupTokenSessionDescriptor { + aiConnection?: import("@paperclipai/shared").AiConnectionLoginIntent; sessionId: string; state: SetupTokenSessionState; environmentId: string; @@ -906,6 +909,7 @@ export class SetupTokenSessionService { try { await this.store.record({ + aiConnection: scope.aiConnection, sessionId, companyId: scope.companyId, ownerUserId: scope.ownerUserId, @@ -1118,6 +1122,7 @@ export class SetupTokenSessionService { environmentId: session.scope.environmentId, deadline: session.deadline, loginUrl: session.loginUrl, + ...(session.scope.aiConnection ? { aiConnection: session.scope.aiConnection } : {}), }; } @@ -1192,6 +1197,7 @@ export class SetupTokenSessionService { if (!durable) return null; return { sessionId: durable.sessionId, + ...(durable.aiConnection ? { aiConnection: durable.aiConnection } : {}), state: durable.state, environmentId: durable.environmentId, deadline: durable.deadline, @@ -1452,6 +1458,7 @@ type AdapterAuthSessionRow = typeof adapterAuthSessions.$inferSelect; function toCleanupRecord(row: AdapterAuthSessionRow): SetupTokenCleanupRecord { return { sessionId: row.publicSessionId, + ...(row.aiConnection ? { aiConnection: row.aiConnection } : {}), companyId: row.companyId, ownerUserId: row.startedByUserId, adapterType: row.adapterType, @@ -1540,6 +1547,7 @@ export function createDbSetupTokenCleanupStore(db: Db): SetupTokenCleanupStore { // service session id, which the service builds from a CSPRNG at start. await db.insert(adapterAuthSessions).values({ companyId: record.companyId, + aiConnection: record.aiConnection, environmentId: record.environmentId, adapterType: record.adapterType as AgentAdapterType, startedByUserId: record.ownerUserId, diff --git a/server/src/services/task-search.ts b/server/src/services/task-search.ts new file mode 100644 index 0000000000..ac5044719a --- /dev/null +++ b/server/src/services/task-search.ts @@ -0,0 +1,196 @@ +import { sql, type SQL } from "drizzle-orm"; +import { COMPANY_SEARCH_MAX_QUERY_LENGTH, COMPANY_SEARCH_MAX_TOKENS } from "@paperclipai/shared"; +import { visibleIssueCondition } from "./issue-visibility.js"; + +// Only grammatical filler is ignored, only in multi-term queries, and never +// inside quotes. Keep negation and domain words (API, UI, PR, etc.) meaningful. +const FILLER = new Set(["a", "an", "the", "and", "of", "to", "for", "in", "on", "with"]); +export function escapeTaskSearchPattern(value: string) { + return value.replace(/[\\%_]/g, "\\$&"); +} + +export function parseTaskSearch(text: string) { + const normalizedQuery = text.slice(0, COMPANY_SEARCH_MAX_QUERY_LENGTH).trim().replace(/\s+/g, " ").toLowerCase(); + const parsed = Array.from(normalizedQuery.matchAll(/"([^"]+)"|([^\s"]+)/g), (match) => ({ + text: match[1] ?? match[2]!, quoted: match[1] !== undefined, + })); + const meaningful = parsed.filter((term) => term.quoted || !FILLER.has(term.text)); + const uniqueTerms = new Map(); + for (const term of meaningful.length > 0 ? meaningful : parsed) { + uniqueTerms.set(term.text, { ...term, quoted: term.quoted || uniqueTerms.get(term.text)?.quoted === true }); + } + const terms = [...uniqueTerms.values()].slice(0, COMPANY_SEARCH_MAX_TOKENS); + const tokens = terms.map((term) => term.text); + const phrase = tokens.join(" "); + // A copied/typed task identifier is navigation, never a fuzzy number match. + const identifier = /^([a-z][a-z0-9]*)[- ](\d+)$/i.exec(normalizedQuery) ?? /^([a-z]+)(\d+)$/i.exec(normalizedQuery); + const identifierQuery = identifier ? `${identifier[1]}-${identifier[2]}` : normalizedQuery; + const patterns = tokens.map((token) => `%${escapeTaskSearchPattern(token)}%`); + const containsPattern = `%${escapeTaskSearchPattern(phrase)}%`; + const startsWithPattern = `${escapeTaskSearchPattern(phrase)}%`; + return { normalizedQuery, terms, tokens, phrase, identifierQuery, patterns, containsPattern, startsWithPattern }; +} +export type TaskSearch = ReturnType; + +function taskSearchAny(field: SQL, search: TaskSearch): SQL { + return search.patterns.length === 0 ? sql`false` + : sql`(${sql.join(search.patterns.map((pattern) => sql`${field} ILIKE ${pattern}`), sql` OR `)})`; +} +// Short typeahead terms must start a word: UI must not match "build", and +// API must not match "Capistrano". Keep an indexable literal precondition. +export function taskSearchTermMatch(field: SQL, search: TaskSearch, index: number): SQL { + const term = search.tokens[index]!; + const literal = sql`${field} ILIKE ${search.patterns[index]!}`; + return /^[\p{L}]{1,3}$/u.test(term) + ? sql`(${literal} AND ${field} ~* ${`(^|[^[:alnum:]])${term}`})` + : literal; +} +export function taskSearchFieldMatch(field: SQL, search: TaskSearch): SQL { + return search.tokens.length === 0 ? sql`false` + : sql`(${sql.join(search.tokens.map((_, index) => taskSearchTermMatch(field, search, index)), sql` OR `)})`; +} +function coverage(matches: SQL[]): SQL { + return matches.length === 0 ? sql`0` + : sql`(${sql.join(matches.map((match) => sql`CASE WHEN ${match} THEN 1 ELSE 0 END`), sql` + `)})`; +} + +// Score bands are deliberately disjoint. Incidental comments, repeated terms, +// status and recency cannot outweigh a stronger kind of match. +export function taskSearchScore(search: TaskSearch): SQL { + const n = search.tokens.length; + if (n === 0) return sql`0`; + return sql`( + CASE + WHEN m.ident_exact THEN 8000 + WHEN m.ident_starts THEN 7000 + WHEN m.title_exact THEN 6000 + WHEN m.title_phrase AND m.title_coverage = ${n} THEN 5000 + WHEN m.title_coverage = ${n} THEN 4000 + WHEN m.issue_coverage = ${n} THEN 3000 + WHEN m.token_coverage = ${n} THEN 2000 + WHEN m.fuzzy_title THEN 1000 + ELSE 0 + END + + m.title_word_coverage * 10 + + CASE WHEN m.title_starts THEN 30 ELSE 0 END + + CASE m.status WHEN 'done' THEN 0 WHEN 'cancelled' THEN 0 ELSE 10 END + )::double precision`; +} + +/** Shared task retrieval for company search and issue-list/command-palette search. + * Uses existing pg_trgm indexes and current rows: no derived corpus or worker. + * The tagged comment/document sets are evaluated once, not once per task. + */ +export function taskSearchCtes(companyId: string, search: TaskSearch, includeContext = true, fallbackFilters?: SQL): SQL { + const n = search.tokens.length; + const comments = n === 0 || !includeContext ? sql`SELECT NULL::uuid AS issue_id, 0 AS ord WHERE false` + : sql.join(search.patterns.map((_, index) => sql` + SELECT c.issue_id, ${index}::int AS ord FROM issue_comments c + WHERE c.company_id = ${companyId} AND c.deleted_at IS NULL AND ${taskSearchTermMatch(sql`c.body`, search, index)} + GROUP BY c.issue_id + `), sql` UNION ALL `); + const documents = n === 0 || !includeContext ? sql`SELECT NULL::uuid AS issue_id, 0 AS ord WHERE false` + : sql.join(search.patterns.map((_, index) => sql` + SELECT d.issue_id, ${index}::int AS ord FROM issue_documents d + JOIN documents body ON body.id = d.document_id AND body.company_id = d.company_id + WHERE d.company_id = ${companyId} AND (${taskSearchTermMatch(sql`body.title`, search, index)} OR ${taskSearchTermMatch(sql`body.latest_body`, search, index)}) + GROUP BY d.issue_id + `), sql` UNION ALL `); + const titleTerms = search.patterns.map((_, index) => taskSearchTermMatch(sql`issues.title`, search, index)); + const issueTerms = search.patterns.map((_, index) => sql`( + ${titleTerms[index]!} OR ${taskSearchTermMatch(sql`issues.identifier`, search, index)} + OR ${taskSearchTermMatch(sql`issues.description`, search, index)} + )`); + const commentTerms = search.patterns.map((_, index) => sql`issues.id IN (SELECT issue_id FROM comment_matches WHERE ord = ${index})`); + const documentTerms = search.patterns.map((_, index) => sql`issues.id IN (SELECT issue_id FROM document_matches WHERE ord = ${index})`); + const allTerms = issueTerms.map((term, index) => sql`(${term} OR ${commentTerms[index]!} OR ${documentTerms[index]!})`); + const phraseMatch = (field: SQL) => n > 0 ? sql`coalesce(${field} ILIKE ${search.containsPattern}, false)` : sql`false`; + const identExact = n > 0 ? sql`lower(issues.identifier) = ${search.identifierQuery}` : sql`false`; + const identStarts = n > 0 ? sql`issues.identifier ILIKE ${escapeTaskSearchPattern(search.identifierQuery) + "%"}` : sql`false`; + const fuzzyAllowed = !search.terms.some((term) => term.quoted) + && search.terms.some((term) => /^[\p{L}]{4,255}$/u.test(term.text)) + && !/^[a-z][a-z0-9]*[- ]?\d+$/i.test(search.normalizedQuery); + const fuzzyTerms = search.terms.map((term, index) => { + if (!/^[\p{L}]{4,255}$/u.test(term.text)) return titleTerms[index]!; + // Bound both arguments before calling fuzzystrmatch (255-character limit). + // Cheap length checks prune word pairs before bounded edit-distance work. + const edits = sql`CASE WHEN least(char_length(word), ${Array.from(term.text).length}) >= 6 THEN 2 + WHEN least(char_length(word), ${Array.from(term.text).length}) >= 5 THEN 1 ELSE 0 END`; + return sql`(${titleTerms[index]!} OR EXISTS ( + SELECT 1 FROM regexp_split_to_table(lower(issues.title), '[^[:alnum:]]+') AS word + WHERE CASE WHEN char_length(word) BETWEEN 4 AND 255 + AND abs(char_length(word) - ${Array.from(term.text).length}) <= ${edits} + THEN levenshtein_less_equal(${term.text}, word, ${edits}) <= ${edits} + ELSE false END + ))`; + }); + const fuzzy = fuzzyAllowed ? sql`CASE WHEN ${coverage(titleTerms)} = ${n} THEN false + ELSE (${sql.join(fuzzyTerms, sql` AND `)}) END` : sql`false`; + const wordTerms = search.tokens.map((token) => { + const escaped = token.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return sql`issues.title ~* ${`(^|[^[:alnum:]_])${escaped}($|[^[:alnum:]_])`}`; + }); + // Carry flags, not potentially large bodies, through materialized stages. + // The search page fetches descriptions only for its result window. + const flags = (fuzzyMatch: SQL) => sql` + SELECT issues.id, issues.identifier, issues.title, + issues.status, issues.priority, issues.assignee_agent_id, issues.assignee_user_id, + issues.project_id, issues.created_at, issues.updated_at, + ${identExact} AS ident_exact, ${identStarts} AS ident_starts, + ${phraseMatch(sql`issues.identifier`)} AS ident_phrase, + ${taskSearchFieldMatch(sql`issues.identifier`, search)} AS ident_token, + ${n > 0 ? sql`lower(issues.title) = ${search.phrase}` : sql`false`} AS title_exact, + ${n > 0 ? sql`issues.title ILIKE ${search.startsWithPattern}` : sql`false`} AS title_starts, + ${phraseMatch(sql`issues.title`)} AS title_phrase, + ${taskSearchFieldMatch(sql`issues.title`, search)} AS title_token, + ${phraseMatch(sql`issues.description`)} AS desc_phrase, + ${taskSearchFieldMatch(sql`issues.description`, search)} AS desc_token, + ${coverage(titleTerms)} AS title_coverage, + ${coverage(wordTerms)} AS title_word_coverage, + ${coverage(issueTerms)} AS issue_coverage, + ${coverage(commentTerms)} AS comment_coverage, + ${coverage(documentTerms)} AS document_coverage, + ${coverage(allTerms)} AS token_coverage, + ${fuzzyMatch} AS fuzzy_title, + issues.id IN (SELECT issue_id FROM comment_matches) AS comment_match, + issues.id IN (SELECT issue_id FROM document_matches) AS document_match + FROM issues + `; + return sql` + WITH comment_matches AS MATERIALIZED (${comments}), + document_matches AS MATERIALIZED (${documents}), + literal_candidates AS MATERIALIZED ( + SELECT issues.id FROM issues + WHERE issues.company_id = ${companyId} AND ${visibleIssueCondition()} + AND ${n === 0 ? sql`${search.normalizedQuery.length === 0}` : sql`( + ${taskSearchAny(sql`issues.title`, search)} + OR ${taskSearchAny(sql`issues.identifier`, search)} + OR ${taskSearchAny(sql`issues.description`, search)} + OR ${identStarts} + )`} + UNION SELECT issue_id FROM comment_matches + UNION SELECT issue_id FROM document_matches + ), search_flags AS MATERIALIZED ( + ${flags(sql`false`)} + WHERE issues.company_id = ${companyId} AND ${visibleIssueCondition()} + AND issues.id IN (SELECT id FROM literal_candidates) + ), literal_matches AS MATERIALIZED ( + SELECT * FROM search_flags + WHERE ${n === 0 ? sql`true` : sql`token_coverage = ${n} OR ident_exact OR ident_starts`} + ), fuzzy_candidates AS MATERIALIZED ( + SELECT issues.id FROM issues + WHERE NOT EXISTS ( + SELECT 1 FROM literal_matches literal + JOIN issues ON issues.id = literal.id + ${fallbackFilters ? sql`WHERE ${fallbackFilters}` : sql``} + ) + AND issues.company_id = ${companyId} AND ${visibleIssueCondition()} + AND ${fuzzy} + ), matched AS MATERIALIZED ( + SELECT * FROM literal_matches + UNION ALL + ${flags(sql`true`)} + WHERE issues.id IN (SELECT id FROM fuzzy_candidates) + ) + `; +} diff --git a/server/src/services/tool-access.ts b/server/src/services/tool-access.ts index 9239377f9e..fe09182a12 100644 --- a/server/src/services/tool-access.ts +++ b/server/src/services/tool-access.ts @@ -1,7 +1,6 @@ -import { - canBrowseProjectRepositoryGrant, - mergeProjectRepository, -} from "./project-repositories.js"; +import { connectionPurposeTransportSchema } from "@paperclipai/shared"; +import { syncConnectionCredentialBindings } from "./connection-credential-bindings.js"; +import { canBrowseProjectRepositoryGrant, mergeProjectRepository } from "./project-repositories.js"; import { captureRunIdentity } from "./run-identity.js"; import { createHash, randomBytes, randomUUID } from "node:crypto"; import { readFileSync } from "node:fs"; @@ -1572,9 +1571,8 @@ function assertClass3ToolCredentialRefAllowed(ref: { } } -function toConnection( - row: typeof toolConnections.$inferSelect, -): ToolConnection { +function toConnection(row: typeof toolConnections.$inferSelect): ToolConnection { + connectionPurposeTransportSchema.parse(row); return { id: row.id, companyId: row.companyId, @@ -7419,6 +7417,7 @@ export function toolAccessService( credentialHeaders?: Record, actor?: ActorInfo, ): Promise { + if (connection.connectionPurpose === "ai") throw unprocessable("AI connections provide runtime authentication, not tool actions"); if (isAgentMailConnection(connection)) { await validateAgentMailConnection(connection); return []; @@ -7433,6 +7432,22 @@ export function toolAccessService( return localTools(connection); } + async function annotateAiGrantHealth(connections: ToolConnection[]) { + const aiConnections = connections.filter(connection => connection.connectionPurpose === "ai"); + if (!aiConnections.length) return; + const grants = await db.select({ connectionId: connectionGrants.connectionId, status: connectionGrants.status }) + .from(connectionGrants).where(and(eq(connectionGrants.companyId, aiConnections[0].companyId), + inArray(connectionGrants.connectionId, aiConnections.map(connection => connection.id)))); + for (const connection of aiConnections) { + const identities = grants.filter(grant => grant.connectionId === connection.id); + if (identities.length && identities.every(grant => grant.status === "revoked")) { + connection.healthStatus = "missing_secret"; + connection.healthMessage = "This credential was revoked. Reconnect the account to restore access."; + connection.requiresReauthorization = true; + } + } + } + async function annotateGitHubAuthorization( connections: ToolConnection[], viewerUserId?: string, @@ -7510,6 +7525,7 @@ export function toolAccessService( actor?: ActorInfo, ): Promise { const connection = await getConnectionRow(connectionId); + if (connection.connectionPurpose === "ai") return { connection: toConnection(connection), runtimeSlot: null }; try { const config = asRecord(connection.config); const oauth = asRecord(config.oauth); @@ -7639,6 +7655,7 @@ export function toolAccessService( } = {}, ): Promise { const connection = await getConnectionRow(connectionId); + if (connection.connectionPurpose === "ai") throw unprocessable("AI connections do not have a tool catalog"); const refreshedAt = now(); let descriptors: McpToolDescriptor[]; try { @@ -8094,6 +8111,7 @@ export function toolAccessService( eq(toolConnections.enabled, true), eq(toolConnections.status, "active"), ne(toolConnections.transport, "chat_sdk"), + ne(toolConnections.transport, "runtime_auth"), ne(toolApplications.type, "paperclip_plugin"), or(isNull(toolConnections.healthCheckedAt), lte(toolConnections.healthCheckedAt, cutoff)), ), @@ -17427,6 +17445,7 @@ export function toolAccessService( for (const connection of connections) { connection.lastUsedAt = lastUsedByConnection.get(connection.id) ?? null; } + await annotateAiGrantHealth(connections); await annotateGitHubAuthorization(connections, viewerUserId); return connections; }, @@ -17552,6 +17571,7 @@ export function toolAccessService( connection.id, connection.companyId, ); + await annotateAiGrantHealth([connection]); await annotateGitHubAuthorization([connection], viewerUserId); return connection; }, @@ -17817,6 +17837,9 @@ export function toolAccessService( ownerUserId: string, ) => { const connection = await getConnectionRow(idOrUid); + if (connection.connectionPurpose === "ai") { + throw badRequest("AI credentials use the connection's human access settings, not agent delegation"); + } return db.transaction(async (tx) => { // Membership removal/suspension takes this same row lock before sweeping // personal grants. Whichever operation wins is therefore authoritative: @@ -18304,7 +18327,7 @@ export function toolAccessService( })), ); } - if (requested.size > 0) { + if (requested.size > 0 && connection.connectionPurpose !== "ai") { const profile = await appProfileForConnection(tx, connection); for (const install of requested.values()) { const [binding] = await tx @@ -18384,6 +18407,7 @@ export function toolAccessService( input: UpdateToolConnection, ): Promise => { const existing = await getConnectionRow(connectionId); + if (existing.connectionPurpose === "ai" && (input.config || input.transportConfig || input.credentialRefs || input.credentialSecretRefs || (input.credentialPolicy && input.credentialPolicy !== existing.credentialPolicy))) throw badRequest("Use AI account reconnect to change credentials. Provider, sign-in method, and ownership cannot be changed."); const config = normalizeGoogleSheetsConnectionConfig( input.config ?? input.transportConfig ?? existing.config, ); @@ -19493,6 +19517,7 @@ export function toolAccessService( input.connectionId, input.companyId, ); + if (connection.connectionPurpose === "ai") throw unprocessable("AI credentials are available only through the runtime resolver"); const application = await getConnectionApplication(connection); const brokerEnabled = connectionTokenBrokerEnabled(connection); const path = brokerEnabled diff --git a/tests/ai-connections-app/app.spec.ts b/tests/ai-connections-app/app.spec.ts new file mode 100644 index 0000000000..1f1036e572 --- /dev/null +++ b/tests/ai-connections-app/app.spec.ts @@ -0,0 +1,226 @@ +import { test, expect } from "@playwright/test"; + +// Opt in against an isolated test drive; never seed or modify a production account. +const companyId = process.env.AI_CONNECTIONS_TEST_COMPANY_ID; +test.skip(!companyId, "Provide the isolated test-drive company ID"); +let prefix: string; + +test.beforeAll(async ({ request }) => { + const response = await request.get(`/api/companies/${companyId}`); + expect(response.ok()).toBe(true); + prefix = (await response.json()).issuePrefix; +}); + +test("existing Connections lists AI providers and keeps account management compact", async ({ page, request }, testInfo) => { + await page.goto(`/${prefix}/apps`); + for (const provider of ["Anthropic", "OpenAI", "OpenRouter", "Grok"]) { + await expect(page.getByRole("button", { name: new RegExp(`^(Add account|Connect) ${provider}$`) })).toBeVisible(); + } + const { connections } = await (await request.get(`/api/companies/${companyId}/ai-connections`)).json(); + const account = connections.find((entry: { ownership: string }) => entry.ownership === "personal"); + expect(account, "The isolated drive should include an imported personal account").toBeTruthy(); + await page.goto(`/${prefix}/apps/${account.id}/permissions`); + await expect(page.getByRole("heading", { name: account.name, exact: true })).toBeVisible(); + await expect(page.getByRole("heading", { name: "Personal default", exact: true })).toBeVisible(); + await expect(page.getByRole("heading", { name: "Agent usage", exact: true })).toHaveCount(0); + await expect(page.getByRole("button", { name: "Back to Connectors", exact: true })).toHaveCount(0); + await expect(page.getByRole("heading", { name: "Which humans can use this credential?" })).toBeVisible(); + await expect(page.getByRole("heading", { name: "Which agents can use this connection?" })).toBeVisible(); + await expect(page.getByText("Authorized use for other users’ tasks", { exact: true })).toHaveCount(0); + await expect(page.getByRole("combobox", { name: "Authorize an agent" })).toHaveCount(0); + await page.screenshot({ path: testInfo.outputPath("account-details.png"), fullPage: true }); + await page.getByRole("button", { name: "Reconnect", exact: true }).last().click(); + await expect(page.getByText("Step 1 of 1", { exact: true })).toBeVisible(); + await expect(page.getByLabel("Connection name")).toBeDisabled(); + await expect(page.getByRole("heading", { name: "Which humans can use this credential?" })).toHaveCount(0); + await page.getByRole("button", { name: "Cancel", exact: true }).last().click(); +}); + +test("rejected API credentials do not create a connection, and cancellation returns to Connections", async ({ page, request }, testInfo) => { + const before = await (await request.get(`/api/companies/${companyId}/ai-connections`)).json(); + await page.goto(`/${prefix}/apps/connect?source=openrouter&method=ai-api_key`); + await page.getByRole("button", { name: /^(Save and continue|Continue)$/ }).click(); + await page.getByLabel("Connection name").fill("Rejected browser test account"); + await page.getByRole("textbox", { name: "API key", exact: true }).fill("invalid-ai-connection-browser-test"); + await page.getByRole("button", { name: "Connect", exact: true }).click(); + await expect(page.getByRole("alert")).toContainText(/rejected|Could not verify|could not verify/); + await expect(page.getByRole("textbox", { name: "API key", exact: true })).toHaveValue(""); + const after = await (await request.get(`/api/companies/${companyId}/ai-connections`)).json(); + expect(after.connections.map((entry: { id: string }) => entry.id).sort()).toEqual(before.connections.map((entry: { id: string }) => entry.id).sort()); + await page.getByRole("button", { name: "Cancel", exact: true }).last().click(); + await expect(page).toHaveURL(new RegExp(`/${prefix}/apps$`)); +}); + +test("legacy adoption and inline account cancellation preserve the agent configuration", async ({ page, request }, testInfo) => { + const agents = await (await request.get(`/api/companies/${companyId}/agents`)).json(); + const agent = agents.find((entry: { adapterType: string; runtimeConfig: { aiConnection?: unknown } }) => ["claude_local", "codex_local", "grok_local"].includes(entry.adapterType) && !entry.runtimeConfig.aiConnection); + expect(agent, "The drive should include a legacy agent for adoption review").toBeTruthy(); + await page.goto(`/${prefix}/agents/${agent.urlKey ?? agent.id}/runtime`); + await page.getByRole("button", { name: "Choose a managed connection", exact: true }).click(); + await expect(page.getByRole("region", { name: "AI connection", exact: true })).toBeVisible(); + await expect(page.getByText("Your personal accounts", { exact: true })).toHaveCount(0); + await page.getByRole("button", { name: "Connect another account", exact: true }).click(); + await expect(page.getByRole("dialog")).toBeVisible(); + await page.getByRole("dialog").getByRole("button", { name: "Back", exact: true }).click(); + await expect(page.getByRole("dialog")).toHaveCount(0); + await expect(page.getByRole("button", { name: "Connect another account", exact: true })).toBeFocused(); + await page.screenshot({ path: testInfo.outputPath("agent-ai-connection.png"), fullPage: true }); + await page.getByRole("button", { name: /Responsible user’s connection/ }).click(); + await expect(page.getByRole("dialog")).toContainText(`Adopt Connections for ${agent.name}`); + await page.getByRole("dialog").getByRole("button", { name: "Cancel", exact: true }).click(); + const after = await (await request.get(`/api/agents/${agent.id}`)).json(); + expect(after.adapterType).toBe(agent.adapterType); + expect(after.adapterConfig).toEqual(agent.adapterConfig); + expect(after.runtimeConfig).toEqual(agent.runtimeConfig); +}); + + +test("connection setup waits for provider details before enabling Continue", async ({ page }) => { + let release!: () => void; + const held = new Promise(resolve => { release = resolve; }); + await page.route(`**/api/companies/${companyId}/tools/gallery`, async route => { + await held; + await route.continue(); + }); + await page.goto(`/${prefix}/apps/connect?source=openrouter&method=ai-api_key`); + const next = page.getByRole("button", { name: /^(Save and continue|Continue)$/ }); + await expect(next).toBeDisabled(); + release(); + await next.click(); + await expect(page.getByLabel("Connection name")).toBeVisible(); + await expect(page).toHaveURL(/stage=setup/); +}); + +test("new OpenRouter agents use the visible binding and provider model catalog", async ({ page }) => { + let tested: { aiConnection?: unknown; adapterConfig?: { model?: string } } | undefined; + await page.route(`**/api/companies/${companyId}/adapters/opencode_local/models*`, async route => { + expect(new URL(route.request().url()).searchParams.get("provider")).toBe("openrouter"); + await route.fulfill({ json: [{ id: "openrouter/anthropic/claude-sonnet-4.5", label: "Claude Sonnet 4.5" }] }); + }); + await page.route(`**/api/companies/${companyId}/adapters/opencode_local/test-environment`, async route => { + tested = route.request().postDataJSON(); + await route.fulfill({ json: { status: "pass", checks: [], testedAt: new Date().toISOString() } }); + }); + await page.goto(`/${prefix}/agents/new?name=OpenRouter+binding+regression&adapterType=opencode_local`); + await expect(page.getByText("Existing authentication — not managed by Connections", { exact: true })).toHaveCount(0); + await expect(page.getByRole("button", { name: /Responsible user’s connection/ })).toHaveAttribute("aria-pressed", "true"); + await page.getByRole("button", { name: "Select model (required)", exact: true }).click(); + await page.getByRole("button", { name: "anthropic/claude-sonnet-4.5", exact: true }).click(); + await page.getByRole("button", { name: "Run test", exact: true }).click(); + await expect.poll(() => tested).toBeTruthy(); + expect(tested?.aiConnection).toEqual({ provider: "openrouter", method: "api_key", mode: "responsible_user" }); + expect(tested?.adapterConfig?.model).toBe("openrouter/anthropic/claude-sonnet-4.5"); +}); + +test("ordinary Anthropic setup keeps the existing tool method available", async ({ page }) => { + await page.goto(`/${prefix}/apps/connect?source=anthropic`); + await page.getByRole("button", { name: /^(Save and continue|Continue)$/ }).click(); + await expect(page.getByText("How do you want to connect?", { exact: true })).toBeVisible(); + await page.getByRole("radio", { name: "Use an API key", exact: true }).click(); + await expect(page.getByLabel("Your Anthropic key", { exact: true })).toBeVisible(); + await expect(page.getByRole("radiogroup", { name: "Connect your model provider" })).toHaveCount(0); + await page.getByRole("button", { name: "Cancel", exact: true }).first().click(); + await expect(page).toHaveURL(new RegExp(`/${prefix}/apps$`)); +}); + +test("explicit OpenAI API method survives continuing and reloading", async ({ page }) => { + await page.goto(`/${prefix}/apps/connect?source=openai&method=ai-api_key`); + await page.getByRole("button", { name: /^(Save and continue|Continue)$/ }).click(); + await expect(page).toHaveURL(/method=ai-api_key/); + await page.reload(); + await page.getByRole("radio", { name: /OpenAI/ }).click(); + await expect(page.getByLabel("API key", { exact: true })).toBeVisible(); + await expect(page.getByText(/CODEX_HOME=/)).toHaveCount(0); + await page.getByRole("button", { name: "Cancel", exact: true }).first().click(); +}); + +for (const [provider, label] of [["anthropic", "Claude"], ["openai", "OpenAI"]]) { + test(`Connections reuses the agent provider step for ${label}`, async ({ page }, testInfo) => { + await page.goto(`/${prefix}/apps/connect?source=${provider}&method=ai-subscription`); + await page.getByRole("button", { name: /^(Save and continue|Continue)$/ }).click(); + await expect(page.getByRole("radiogroup", { name: "Connect your model provider" })).toBeVisible(); + await expect(page.getByRole("button", { name: "Connect for tool access instead" })).toHaveCount(0); + await page.getByRole("radio", { name: new RegExp(label) }).click(); + if (provider === "anthropic") { + await expect(page.getByText(/Connect uses your local/)).toBeVisible(); + await expect(page.getByText("claude auth login", { exact: true })).toBeVisible(); + } else { + await expect(page.getByText(/Your existing terminal login stays separate/)).toBeVisible(); + const command = page.getByText(/^CODEX_HOME=.* codex login$/); + await expect(command).toBeVisible(); + const preparedCommand = await command.textContent(); + await page.reload(); + await page.getByRole("radio", { name: new RegExp(label) }).click(); + await expect(command).toHaveText(preparedCommand!); + } + await expect(page.getByRole("button", { name: "Connect", exact: true })).toBeEnabled(); + // Let the shared tile-collapse and card-enter animations settle for visual review. + await page.waitForTimeout(1000); + await page.screenshot({ path: testInfo.outputPath(`${provider}-shared-provider-step.png`), fullPage: true }); + await page.getByRole("button", { name: "Back", exact: true }).click(); + await page.getByRole("button", { name: "Use API key instead", exact: true }).click(); + await page.getByRole("radio", { name: new RegExp(label) }).click(); + await expect(page.getByLabel("API key", { exact: true })).toBeVisible(); + await expect(page.getByText(/Provide your .* API key to connect/)).toBeVisible(); + await page.getByRole("button", { name: "Cancel", exact: true }).first().click(); + await expect(page).toHaveURL(new RegExp(`/${prefix}/apps$`)); + }); +} + +// Exercise the real onboarding login controllers; only provider/server replies +// are simulated. No credentials, sandbox leases or accounts are created here. +for (const [provider, label, adapter] of [["anthropic", "Claude", "claude_local"], ["openai", "OpenAI", "codex_local"]]) { + test(`Connections uses onboarding browser sign-in for ${label}`, async ({ page }, testInfo) => { + const environmentId = "11111111-1111-4111-8111-111111111111"; + const sessionId = "22222222-2222-4222-8222-222222222222"; + const base = `/api/companies/${companyId}`; + const sessions = provider === "anthropic" ? `${base}/setup-token-login-sessions` : `${base}/adapters/${adapter}/login-sessions`; + let starts = 0; + let cancels = 0; + let intent: Record | undefined; + const session = () => ({ sessionId, environmentId, adapterType: adapter, status: provider === "anthropic" ? "awaiting_code" : "awaiting_user", expiresAt: new Date(Date.now() + 300000).toISOString(), aiConnection: intent, prompt: provider === "anthropic" ? { authorizationUrl: "https://provider.example/authorize" } : { url: "https://provider.example/authorize", code: "ABCD-EFGH" } }); + await page.route(`**${base}/environments`, route => route.fulfill({ json: [{ id: environmentId, name: "Browser sign-in test sandbox", driver: "sandbox", status: "active", config: { provider: "browser-test" } }] })); + await page.route(`**${base}/environments/capabilities`, route => route.fulfill({ json: { sandboxProviders: { "browser-test": { supportsLoginPty: true } } } })); + await page.route(`**${sessions}**`, async route => { + const path = new URL(route.request().url()).pathname; + if (path.endsWith("/active")) return route.fulfill(starts ? { json: session() } : { status: 404, json: { error: "Not found" } }); + if (path.endsWith("/cancel")) { cancels++; return route.fulfill({ json: {} }); } + if (path.endsWith("/prompt")) return route.fulfill({ json: { authorizationUrl: "https://provider.example/authorize" } }); + if (path === sessions && route.request().method() === "POST") { + starts++; + const payload = route.request().postDataJSON(); + expect(payload.environmentId).toBe(environmentId); + expect(payload.aiConnection.provider).toBe(provider); + intent = payload.aiConnection; + } + return route.fulfill({ json: session() }); + }); + await page.addInitScript(() => { window.open = (url) => { (window as unknown as { loginDestination: string }).loginDestination = String(url); return null; }; }); + await page.goto(`/${prefix}/apps/connect?source=${provider}&method=ai-subscription`); + await page.getByRole("button", { name: /^(Save and continue|Continue)$/ }).click(); + await page.getByRole("radio", { name: new RegExp(label) }).click(); + const signIn = page.getByRole("button", { name: `Sign in to ${label}`, exact: true }); + await expect(signIn).toBeEnabled(); + await expect(page.getByText(/login on this machine/)).toHaveCount(0); + if (provider === "anthropic") await expect(page.locator('input[type="password"]')).toBeVisible(); + else await expect(page.getByText("ABCD-EFGH", { exact: true })).toBeVisible(); + await signIn.click(); + expect(await page.evaluate(() => (window as unknown as { loginDestination: string }).loginDestination)).toBe("https://provider.example/authorize"); + await expect(page.getByRole("button", { name: "Waiting for code", exact: true })).toBeDisabled(); + // The shared footer label animates; capture after it has settled. + await page.waitForTimeout(600); + await page.screenshot({ path: testInfo.outputPath(`${provider}-onboarding-browser-login.png`), fullPage: true }); + if (provider === "anthropic") { + const submitted = page.waitForRequest(request => request.url().endsWith(`${sessionId}/code`) && request.method() === "POST"); + await page.locator('input[type="password"]').fill("storybook-fixture-code"); + await page.locator('input[type="password"]').press("Enter"); + await submitted; + await expect(page.getByRole("button", { name: "Connecting", exact: true })).toBeDisabled(); + } + await page.getByRole("button", { name: "Back", exact: true }).click(); + await page.getByRole("radio", { name: new RegExp(label) }).click(); + await expect(page.getByRole("button", { name: `Sign in to ${label}`, exact: true })).toBeEnabled(); + expect(starts).toBe(1); + expect(cancels).toBe(0); + }); +} diff --git a/tests/ai-connections-app/inline-repair.live.spec.ts b/tests/ai-connections-app/inline-repair.live.spec.ts new file mode 100644 index 0000000000..95bea5bf75 --- /dev/null +++ b/tests/ai-connections-app/inline-repair.live.spec.ts @@ -0,0 +1,90 @@ +import { test, expect } from "@playwright/test"; + +// Explicitly opt in with a disposable task/account and a real provider key. +// All writes use the UI. API reads only verify identity and execution outcomes. +const disposableMarker = process.env.AI_REPAIR_TEST_DISPOSABLE_MARKER; +const destructiveOptIn = process.env.AI_REPAIR_TEST_ALLOW_DESTRUCTIVE === "1"; +const companyId = process.env.AI_CONNECTIONS_TEST_COMPANY_ID; +const issueId = process.env.AI_REPAIR_TEST_ISSUE_ID; +const connectionId = process.env.AI_REPAIR_TEST_CONNECTION_ID; +const providerKey = process.env.AI_REPAIR_TEST_KEY; +test.use({ trace: "off", video: "off" }); +test.skip(!destructiveOptIn || !disposableMarker || !companyId || !issueId || !connectionId || !providerKey, "Live repair requires explicit disposable fixtures and a provider key"); + +test("repair the selected AI account inside the task and continue without another message", async ({ page, request }, testInfo) => { + test.setTimeout(240_000); + if (process.env.AI_REPAIR_TEST_NARROW === "1") await page.setViewportSize({ width: 390, height: 844 }); + // Fail before any mutation unless every target belongs to the same explicitly + // marked disposable fixture. Never run this scenario against a remote host. + const origin = new URL(testInfo.project.use.baseURL!); + expect(origin.protocol).toBe("http:"); + expect(["127.0.0.1", "[::1]"]).toContain(origin.hostname); + expect(disposableMarker).toMatch(/^[a-f0-9]{32}$/); + const fixtureName = `AI Repair QA ${disposableMarker}`; + const health = await (await request.get("/api/health")).json(); + expect(health.deploymentMode).toBe("local_trusted"); + const companies = await (await request.get("/api/companies")).json(); + const company = companies.find((company: { id: string }) => company.id === companyId); + expect(company).toMatchObject({ id: companyId, name: fixtureName }); + const prefix = company.issuePrefix; + const taskBefore = await (await request.get(`/api/issues/${issueId}`)).json(); + const agentBefore = await (await request.get(`/api/agents/${taskBefore.assigneeAgentId}`)).json(); + expect(taskBefore).toMatchObject({ companyId, title: fixtureName }); + expect(agentBefore).toMatchObject({ companyId, name: fixtureName, adapterType: "codex_local" }); + const agents = await (await request.get(`/api/companies/${companyId}/agents`)).json(); + expect(agents.map((agent: { id: string }) => agent.id)).toEqual([agentBefore.id]); + const list = async () => (await (await request.get(`/api/companies/${companyId}/ai-connections`)).json()).connections; + const before = await list(); + const accountBefore = before.find((connection: { id: string }) => connection.id === connectionId); + expect(before).toHaveLength(1); + expect(accountBefore).toMatchObject({ companyId, name: fixtureName, provider: "openai", method: "api_key", ownership: "personal" }); + expect(accountBefore.isDefault).toBe(true); + expect(["connected", "revoked"]).toContain(accountBefore.status); + + await page.goto(`/${prefix}/apps/${connectionId}/permissions`); + if (accountBefore.status === "connected") { + await page.getByRole("button", { name: "Revoke identity", exact: true }).click(); + await page.getByRole("alertdialog").getByRole("button", { name: "Revoke identity", exact: true }).click(); + } + await expect(page.locator("header").getByText("Revoked", { exact: true })).toBeVisible(); + await page.goto(`/${prefix}/issues/${issueId}`); + let proof = `QA-IN-CARD-REPAIR-${Date.now()}: 1147`; + const pending = (await (await request.get(`/api/issues/${issueId}/interactions`)).json()).some((interaction: {kind: string; status: string; payload: {purpose?: string}}) => interaction.kind === "connection_intent" && interaction.status === "pending" && interaction.payload.purpose === "ai"); + if (pending) { + const comments = await (await request.get(`/api/issues/${issueId}/comments`)).json(); + proof = comments.map((comment: {body: string}) => comment.body.match(/QA-IN-CARD-REPAIR-\d+: 1147/)?.[0]).filter(Boolean).at(-1); + expect(proof).toBeTruthy(); + } else { + await page.getByRole("textbox", { name: "editable markdown" }).fill(`Inline repair acceptance: calculate 31 * 37. Post exactly ${proof}, then mark Done. This first attempt should block on my revoked default; I will reconnect it inside this task. Do not change configuration, create subtasks, or modify files.`); + await page.getByRole("button", { name: "Send", exact: true }).click(); + } + const fix = page.getByRole("button", { name: "Fix connection", exact: true }); + await expect(fix).toBeVisible({ timeout: 60_000 }); + await fix.click(); + const inline = page.getByTestId("ai-connection-inline-repair"); + await expect(inline.getByLabel("Connection name")).toHaveValue(accountBefore.name); + await expect(inline.getByLabel("Connection name")).toBeDisabled(); + await expect(page.getByRole("dialog")).toHaveCount(0); + await inline.getByRole("button", { name: /^(Back|Cancel)$/ }).click(); + await expect(page.getByTestId("ai-connection-inline-repair")).toHaveCount(0); + await expect(page.getByTestId("connection-intent-focus-target").filter({ has: fix })).toBeFocused(); + await fix.click(); + await inline.getByRole("radio", { name: "OpenAI API", exact: true }).click(); + const keyField = inline.getByPlaceholder("Enter API key here"); + await expect(keyField).toBeVisible(); + await inline.screenshot({ path: testInfo.outputPath("inline-repair-before.png") }); + // Never include credentials in a failed action's error/trace output. + try { await keyField.fill(providerKey!); } catch { throw new Error("Could not fill the private credential field"); } + await inline.getByRole("button", { name: "Connect", exact: true }).click(); + await expect(page.getByTestId("ai-connection-inline-repair")).toHaveCount(0, { timeout: 60_000 }); + await expect(page.getByText(proof, { exact: true })).toBeVisible({ timeout: 120_000 }); + await expect.poll(async () => (await (await request.get(`/api/issues/${issueId}`)).json()).status).toBe("done"); + const after = await list(); + expect(after).toHaveLength(before.length); + expect(after.find((connection: { id: string }) => connection.id === connectionId)).toMatchObject({ id: connectionId, grantId: accountBefore.grantId, isDefault: true, status: "connected" }); + const agentAfter = await (await request.get(`/api/agents/${taskBefore.assigneeAgentId}`)).json(); + expect(agentAfter.adapterType).toBe(agentBefore.adapterType); + expect(agentAfter.adapterConfig).toEqual(agentBefore.adapterConfig); + expect(agentAfter.runtimeConfig).toEqual(agentBefore.runtimeConfig); + await page.screenshot({ path: testInfo.outputPath("inline-repair-completed.png"), fullPage: true }); +}); diff --git a/tests/ai-connections-app/playwright.config.ts b/tests/ai-connections-app/playwright.config.ts new file mode 100644 index 0000000000..4d2c474814 --- /dev/null +++ b/tests/ai-connections-app/playwright.config.ts @@ -0,0 +1,16 @@ +import { defineConfig } from "@playwright/test"; + +export default defineConfig({ + testDir: ".", + testIgnore: process.env.AI_REPAIR_TEST_ALLOW_DESTRUCTIVE === "1" ? [] : ["**/*.live.spec.ts"], + outputDir: "./test-results", + timeout: 45_000, + workers: 1, + reporter: "list", + use: { + baseURL: process.env.AI_CONNECTIONS_TEST_URL ?? "http://127.0.0.1:3100", + browserName: "chromium", + reducedMotion: "reduce", + screenshot: "only-on-failure", + }, +}); diff --git a/tests/ai-connections-review/playwright.config.ts b/tests/ai-connections-review/playwright.config.ts new file mode 100644 index 0000000000..75b89da5f8 --- /dev/null +++ b/tests/ai-connections-review/playwright.config.ts @@ -0,0 +1,19 @@ +import { defineConfig } from "@playwright/test"; + +export default defineConfig({ + testDir: ".", + outputDir: "./test-results", + timeout: 30_000, + workers: 2, + reporter: "list", + use: { + baseURL: "http://localhost:6116", + browserName: "chromium", + reducedMotion: "reduce", + }, + webServer: { + command: "node ../../scripts/serve-storybook-static.mjs --port 6116", + url: "http://localhost:6116/index.json", + reuseExistingServer: true, + }, +}); diff --git a/tests/ai-connections-review/review.spec.ts b/tests/ai-connections-review/review.spec.ts new file mode 100644 index 0000000000..026c6d4634 --- /dev/null +++ b/tests/ai-connections-review/review.spec.ts @@ -0,0 +1,157 @@ +import { readFileSync } from "node:fs"; +import { test, expect } from "@playwright/test"; + +const index = JSON.parse( + readFileSync( + new URL("../../ui/storybook-static/index.json", import.meta.url), + "utf8", + ), +); +const stories = Object.keys(index.entries).filter((id) => + id.startsWith("ai-connections-review--"), +); + +for (const id of stories) { + test(id, async ({ page }) => { + const errors: string[] = []; + page.on("pageerror", (error) => errors.push(error.message)); + page.on("console", (message) => { + if ( + message.type() === "error" && + !message.text().startsWith("Failed to load resource") + ) + errors.push(message.text()); + }); + await page.route("**/*", (route) => + new URL(route.request().url()).hostname === "localhost" + ? route.continue() + : route.abort(), + ); + await page.goto(`/iframe.html?id=${id}&viewMode=story`); + // Completing follows the awaited play function, including its assertions. + await page.waitForFunction(() => { + const preview = ( + window as unknown as { + __STORYBOOK_PREVIEW__?: { currentRender?: { phase: string } }; + } + ).__STORYBOOK_PREVIEW__; + return ["completing", "completed", "finished", "errored"].includes( + preview?.currentRender?.phase ?? "", + ); + }); + await expect + .poll(() => + page.evaluate(() => + Boolean( + document.querySelector("#storybook-root")?.textContent || + document.querySelector('[role="dialog"]')?.textContent, + ), + ), + ) + .toBe(true); + expect(errors, `Story/play errors for ${id}`).toEqual([]); + await expect(page.getByTestId("ai-review-frame")).toContainText("Already in the app"); + await expect(page.getByTestId("ai-review-frame")).toContainText("Storybook simulation"); + if (id.endsWith("responsible-user")) { + await expect(page.getByTestId("ai-review-preview")).toContainText("Example page context · Storybook only"); + await expect(page.getByTestId("ai-component-boundary")).toContainText("App component: AiConnectionPicker"); + await expect(page.getByText("Your personal accounts", { exact: true })).toHaveCount(0); + await expect(page.getByRole("button", { name: /Make default|Authorize for/ })).toHaveCount(0); + await expect(page.getByText("For you: My Claude subscription", { exact: true })).toBeVisible(); + } + if (id.endsWith("review-index")) { + const links = await page + .locator("#storybook-root a") + .evaluateAll((anchors) => + anchors.map((anchor) => (anchor as HTMLAnchorElement).href), + ); + for (const href of links) { + const url = new URL(href); + expect(url.pathname).toBe("/"); + expect( + index.entries[url.searchParams.get("path")!.replace("/story/", "")], + ).toBeTruthy(); + } + } + }); +} + +for (const theme of ["light", "dark"]) { + for (const width of [390, 1200]) { + test(`layout ${theme} ${width}`, async ({ page }, testInfo) => { + await page.setViewportSize({ width, height: 960 }); + for (const story of [ + "responsible-user", + "claude-subscription", + "identity-matrix", + "management", + ]) { + await page.goto( + `/iframe.html?id=ai-connections-review--${story}&viewMode=story&globals=theme:${theme}`, + ); + await expect + .poll(() => + page.evaluate(() => + Boolean( + document.querySelector("#storybook-root")?.textContent || + document.querySelector('[role="dialog"]')?.textContent, + ), + ), + ) + .toBe(true); + if (story === "management") await expect(page.getByLabel("AI account settings")).toBeVisible(); + if (story === "identity-matrix") await expect(page.getByRole("button", { name: "Add account Anthropic" })).toBeVisible(); + await page.evaluate(() => document.fonts.ready); + await expect + .poll(() => + page.evaluate( + () => document.documentElement.scrollWidth <= window.innerWidth, + ), + ) + .toBe(true); + await page.screenshot({ + path: testInfo.outputPath(`${story}-${theme}-${width}.png`), + fullPage: true, + animations: "disabled", + }); + } + }); + } +} + +test("shared connection chooser keyboard navigation preserves runtime", async ({ page }) => { + await page.goto( + "/iframe.html?id=ai-connections-review--responsible-user&viewMode=story", + ); + const first = page.getByRole("button", { name: "Responsible user’s connection", exact: true }); + await first.focus(); + await page.keyboard.press("Tab"); + await page.keyboard.press("Enter"); + await expect( + page.getByRole("button", { name: "Engineering Claude", exact: true }), + ).toHaveAttribute("aria-pressed", "true"); + await expect( + page.getByRole("button", { name: "Engineering Claude", exact: true }), + ).toBeFocused(); + await expect(page.getByTestId("ai-harness")).toHaveText("Claude Code"); + await expect(page.getByTestId("ai-model")).toHaveText( + "Configured Claude model", + ); +}); + +// Keep upstream's real saved-account and login workflow in this integration's +// review gate as well as the AI-specific component stories above. +for (const id of Object.keys(index.entries).filter((entry) => entry.startsWith("onboarding-saved-connections--"))) { + test(`upstream ${id}`, async ({ page }) => { + const errors: string[] = []; + page.on("pageerror", error => errors.push(error.message)); + await page.goto(`/iframe.html?id=${id}&viewMode=story`); + await page.waitForFunction(() => { + const preview = (window as unknown as { __STORYBOOK_PREVIEW__?: { currentRender?: { phase: string } } }).__STORYBOOK_PREVIEW__; + return ["completed", "finished", "errored"].includes(preview?.currentRender?.phase ?? ""); + }); + await expect(page.locator("#storybook-root")).not.toBeEmpty(); + expect(errors).toEqual([]); + expect(await page.evaluate(() => (window as unknown as { __STORYBOOK_PREVIEW__?: { currentRender?: { phase: string } } }).__STORYBOOK_PREVIEW__?.currentRender?.phase)).not.toBe("errored"); + }); +} diff --git a/tests/e2e/chat-adapters-ui.spec.ts b/tests/e2e/chat-adapters-ui.spec.ts index 11bfec92a6..1b35811a51 100644 --- a/tests/e2e/chat-adapters-ui.spec.ts +++ b/tests/e2e/chat-adapters-ui.spec.ts @@ -17,7 +17,7 @@ import { * because those checks require real accounts and publicly reachable ingress. */ -type Provider = "slack" | "github" | "discord" | "microsoft-teams" | "telegram"; +type Provider = "slack" | "github" | "discord" | "microsoft-teams" | "telegram" | "imessage-photon"; const GITHUB_PRIVATE_KEY_FIXTURE = "-----BEGIN PRIVATE KEY-----\nlocal-e2e-key\n-----END PRIVATE KEY-----\n"; @@ -125,6 +125,7 @@ const PROVIDER_LIFECYCLE_COPY: Record< Provider, { reconnect: string; remove: string } > = { + "imessage-photon": { reconnect: "Reconnect the same dedicated Photon number", remove: "does not delete the Photon project" }, slack: { reconnect: "Reconnect verifies or replaces credentials for this same Slack app. It does not reinstall the app or change its workspace or channel membership.", @@ -335,7 +336,8 @@ async function installChatControlPlaneMock( { enableChatConnectors, resourceCount = 2, - }: { enableChatConnectors: boolean; resourceCount?: number }, + photonShared = false, + }: { enableChatConnectors: boolean; resourceCount?: number; photonShared?: boolean }, ): Promise { const endpoint = endpointFixture(provider, seed); const state: ChatMock & { @@ -543,12 +545,16 @@ async function installChatControlPlaneMock( } else { expect(provider.provider).toBe("slack"); } + if (provider.provider === "imessage-photon") { + expect(body.photon).toEqual(photonShared ? {allocation:"shared",projectId:"project-e2e"} : {allocation:"dedicated",projectId:"project-e2e",lineId:"line-one"}); + } Object.assign(endpoint, { + ...(provider.provider === "imessage-photon" ? {photonAllocation: photonShared ? "shared" : "dedicated", allowGroupChats: !photonShared} : {}), status: "verifying", providerAccountId: `account-${provider.provider}`, providerAccountLabel: provider.accountLabel, - botExternalId: `bot-${provider.provider}`, - botUsername: provider.botUsername, + botExternalId: provider.provider === "imessage-photon" ? photonShared ? "photon-project:project-e2e" : "+15555550100" : `bot-${provider.provider}`, + botUsername: photonShared ? null : provider.botUsername, botLabel: provider.botLabel, setup: { ...endpoint.setup, @@ -562,6 +568,10 @@ async function installChatControlPlaneMock( return; } + if (pathname === `/api/chat-endpoints/${endpoint.id}/photon/inspect` && method === "POST") { + expect(bodyOf(route)).toEqual({projectId:"project-e2e",projectSecret:"photon-test-secret"}); + await fulfill(route,{projectId:"project-e2e",projectName:"Photon Test",allocation:photonShared ? "shared" : "dedicated",eligible:true,lines:photonShared ? [] : [{lineId:"line-one",phoneNumber:"+15555550100",eligible:true},{lineId:"line-two",phoneNumber:"+15555550102",eligible:true}]}); return; + } if ( pathname === `/api/chat-endpoints/${endpoint.id}/test` && method === "POST" @@ -681,7 +691,7 @@ async function installChatControlPlaneMock( externalConversationId: `external-conversation-${provider.provider}`, externalThreadId: `external-thread-${provider.provider}`, externalLabel: provider.resourceLabel, - externalUrl: provider.externalUrl, + externalUrl: provider.provider === "imessage-photon" ? null : provider.externalUrl, isDirectMessage: provider.provider === "telegram", state: state.conversationState, lastPublicationStatus: "published", @@ -1531,7 +1541,6 @@ test.describe.serial("native chat adapter UI", () => { ).toBeVisible(); await expectSetupRail(page); await selectMaya(page); - // The selection schedules a request; clicking alone does not await it. await expect.poll(() => mock.createdWithAgentId).toBe(seed.agentId); expect(mock.createdWithAgentId).not.toBe(seed.otherAgentId); await expect( @@ -3341,3 +3350,136 @@ test.describe("Exact failed chat run retry", () => { } } }); + + +test.describe("iMessage Photon setup and management", () => { + let seed: Seed; + const photon: ProviderCase = { + provider: "imessage-photon", + slug: "imessage-photon", + name: "iMessage Photon", + accountLabel: "Photon Test", + botLabel: "Maya", + botUsername: "+15555550100", + resourceLabel: "Family project", + secondaryResourceLabel: "Second group", + resourceType: "group_chat", + externalUrl: "https://app.photon.codes/", + setupHeading: /Connect iMessage Photon/, + setupButton: "Connect selected number", + chatAndTool: false, + }; + test.beforeAll(async ({ request }) => { + seed = await seedCompanyAndAgent(request); + }); + test("connects Pro shared DMs without presenting a fake owned number or enabling groups", async ({page}) => { + await installChatControlPlaneMock(page, photon, seed, { enableChatConnectors: true, photonShared: true }); + await page.goto(`/${seed.prefix}/apps`); + await page.getByRole("button", {name:"Connect iMessage Photon",exact:true}).click(); + await selectMaya(page); + await page.getByLabel("Project ID").fill("project-e2e"); + await page.getByLabel("Project secret").fill("photon-test-secret"); + await page.getByRole("button", {name:"Inspect Photon project"}).click(); + await expect(page.getByText(/Groups cannot be enabled on this channel/)).toBeVisible(); + await page.getByRole("button", {name:"Connect shared DMs"}).click(); + await expect(page.getByRole("heading", {name:"Try Maya in iMessage Photon"})).toBeVisible(); + await page.reload(); + await expect(page.getByText(/enroll your sender in Users/)).toBeVisible(); + await expect(page.getByRole("button", {name:/Copy \+1555/})).toHaveCount(0); + await page.getByRole("button", {name:"I've sent the test message"}).click(); + await page.getByRole("tab", {name:"Settings"}).click(); + await expect(page.getByText(/Shared Photon project · direct messages only/)).toBeVisible(); + await expect(page.getByRole("switch", {name:"Enable Family project"})).toBeDisabled(); + await expect(page.getByRole("button", {name:"Copy dedicated number"})).toHaveCount(0); + }); + for (const theme of ["light", "dark"] as const) { + test(`discovers dedicated lines and completes the channel wizard (${theme})`, async ({ + page, + }, testInfo) => { + const mock = await installChatControlPlaneMock(page, photon, seed, { + enableChatConnectors: true, + }); + await page.addInitScript( + (value) => localStorage.setItem("paperclip.theme", value), + theme, + ); + await page.goto(`/${seed.prefix}/apps`); + const card = page.locator( + '[role="listitem"][data-app-slug="imessage-photon"]', + ); + await expect(card).toBeVisible(); + await card + .getByRole("button", { name: "Connect iMessage Photon" }) + .click(); + await selectMaya(page); + await expectSetupRail(page); + await expect( + page.getByRole("heading", { name: "Connect iMessage Photon" }), + ).toBeVisible(); + await page.getByLabel("Project ID").fill("project-e2e"); + await page.getByLabel("Project secret").fill("photon-test-secret"); + await expect(page.getByLabel("Project secret")).toHaveAttribute( + "type", + "password", + ); + await page + .getByRole("button", { name: "Inspect Photon project" }) + .click(); + await expect( + page.getByRole("button", { name: "Connect selected number" }), + ).toBeDisabled(); + await page + .getByRole("radio", { name: "+15555550100", exact: true }) + .focus(); + await page.keyboard.press("Space"); + await page + .getByRole("button", { name: "Connect selected number" }) + .click(); + expect(mock.configuredCredentialKeys).toEqual(["projectSecret"]); + await expect( + page.getByRole("heading", { name: "Try Maya in iMessage Photon" }), + ).toBeVisible(); + await expect( + page.getByRole("button", { name: "Copy +15555550100" }), + ).toBeVisible(); + await expect( + page.getByText("Link your Messages identity", { exact: true }), + ).toBeVisible(); + await page + .getByRole("button", { name: "I've sent the test message" }) + .click(); + await page.getByRole("tab", { name: "Settings" }).click(); + await expect( + page.getByText(/replies are visible to everyone in that group/), + ).toBeVisible(); + const group = page.getByRole("switch", { name: "Enable Family project" }); + await expect(group).not.toBeChecked(); + await group.click(); + await expect(group).toBeChecked(); + await page.setViewportSize({ width: 390, height: 844 }); + await page + .getByRole("combobox", { name: "Page section" }) + .selectOption("activity"); + await expect( + page.getByRole("button", { name: "Pause", exact: true }), + ).toBeVisible(); + await page.screenshot({ + path: testInfo.outputPath(`photon-${theme}-mobile.png`), + fullPage: true, + }); + expect( + await page.evaluate( + () => document.documentElement.scrollWidth <= window.innerWidth, + ), + ).toBe(true); + await page.getByRole("button", { name: "Pause", exact: true }).click(); + await expect( + page.getByRole("button", { name: "Resume", exact: true }), + ).toBeVisible(); + await page.getByRole("button", { name: "Resume", exact: true }).click(); + await expect( + page.getByRole("button", { name: "Pause", exact: true }), + ).toBeVisible(); + }); + } +}); diff --git a/tests/e2e/conference-room-typing-intro.spec.ts b/tests/e2e/conference-room-typing-intro.spec.ts index 294aa4b0d1..b423537efe 100644 --- a/tests/e2e/conference-room-typing-intro.spec.ts +++ b/tests/e2e/conference-room-typing-intro.spec.ts @@ -1,4 +1,5 @@ import { test, expect, type Page } from "@playwright/test"; +import { mockOnboardingLocalAiConnection } from "./helpers/onboarding-ai-connection"; import { expectLandsOnFirstTaskWithoutDashboardBounce, instrumentNavLog, @@ -21,11 +22,12 @@ import { const FIRST_TASK_TITLE = "Paperclip onboarding"; /** - * Intercept the two side-effecting calls the wizard makes so no real CLI check + * Intercept authentication, environment checks, and hiring so no real CLI check * runs and no real agent process spawns (the hire still happens server-side * with an inert http adapter). */ async function installLaunchIntercepts(page: Page, baseURL?: string) { + await mockOnboardingLocalAiConnection(page); await page.route("**/test-environment", (route) => route.fulfill({ contentType: "application/json", @@ -93,9 +95,8 @@ async function runOnboardingWizard(page: Page, companyName: string) { await source.click(); // "Connect", not "Next": this step's button starts the sign-in where there - // is one to start, so it is named for what it does. Here there is none — - // this instance has no sandbox environment, so the step has no login to - // offer and Connect goes straight to the hire. + // is one to start, so it is named for what it does. This test simulates + // successful local account connection before the environment check and hire. // // Waited on for enabled rather than for visible: it is already on screen, // disabled, and clicking a disabled button raises nothing and does nothing. diff --git a/tests/e2e/helpers/onboarding-ai-connection.ts b/tests/e2e/helpers/onboarding-ai-connection.ts new file mode 100644 index 0000000000..34bd690e5d --- /dev/null +++ b/tests/e2e/helpers/onboarding-ai-connection.ts @@ -0,0 +1,14 @@ +import { randomUUID } from "node:crypto"; +import { expect, type Page } from "@playwright/test"; + +/** First-task tests exercise the real wizard and hire/task APIs with an inert + * adapter. Simulate successful provider authentication without using host auth. */ +export async function mockOnboardingLocalAiConnection(page: Page) { + const connectionId = randomUUID(); + const grantId = randomUUID(); + await page.route("**/ai-connections/local", async (route) => { + expect(route.request().method()).toBe("POST"); + expect(route.request().postDataJSON()).toMatchObject({ method: "subscription", ownership: "personal" }); + await route.fulfill({ json: { connectionId, grantId } }); + }); +} diff --git a/tests/e2e/onboarding.spec.ts b/tests/e2e/onboarding.spec.ts index b425051524..24d54928c9 100644 --- a/tests/e2e/onboarding.spec.ts +++ b/tests/e2e/onboarding.spec.ts @@ -1,4 +1,5 @@ import { test, expect } from "@playwright/test"; +import { mockOnboardingLocalAiConnection } from "./helpers/onboarding-ai-connection"; /** * E2E: Onboarding wizard flow (NUX Phase 2 expanded wizard). @@ -389,15 +390,19 @@ test.describe("Onboarding wizard", () => { // route, and keeping the pre-Connect part of this test the same as the // ordinary sign-in test above. let sessionStarted = false; + let aiConnection: Record | undefined; await page.route("**/setup-token-login-sessions", (route) => { if (route.request().method() === "POST") { startCalls += 1; sessionStarted = true; + aiConnection = route.request().postDataJSON().aiConnection; } return route.fulfill({ contentType: "application/json", body: JSON.stringify({ sessionId: SESSION_ID, + environmentId: FAKE_SANDBOX_ENVIRONMENT_ID, + aiConnection, status: "pending", expiresAt: new Date(Date.now() + 600_000).toISOString(), }), @@ -418,6 +423,8 @@ test.describe("Onboarding wizard", () => { contentType: "application/json", body: JSON.stringify({ sessionId: SESSION_ID, + environmentId: FAKE_SANDBOX_ENVIRONMENT_ID, + aiConnection, status: "pending", expiresAt: new Date(Date.now() + 600_000).toISOString(), }), @@ -438,6 +445,8 @@ test.describe("Onboarding wizard", () => { contentType: "application/json", body: JSON.stringify({ sessionId: SESSION_ID, + environmentId: FAKE_SANDBOX_ENVIRONMENT_ID, + aiConnection, status: "pending", expiresAt: new Date(Date.now() + 600_000).toISOString(), panelMode: "submitted_browser_code", @@ -512,14 +521,12 @@ test.describe("Onboarding wizard", () => { expect(pageErrors, pageErrors.join("\n")).toHaveLength(0); }); - test("connect step blocks the hire when the environment probe fails and no sign-in is needed", async ({ + test("connect step blocks the hire when the environment probe fails after account connection", async ({ page, }) => { - // The other half of what the test above used to cover. The two claims are - // different situations now: there, an absent credential makes Connect start - // a sign-in; here there is no sandbox to sign in against — this throwaway - // instance only auto-creates the local environment, and nothing below adds - // one — so Connect goes straight to the probe, and the probe is the gate. + // A successful local account connection still requires a passing + // environment probe before the wizard may hire the agent. + await mockOnboardingLocalAiConnection(page); const pageErrors: string[] = []; page.on("pageerror", (err) => pageErrors.push(err.message)); diff --git a/tests/e2e/planning-mode-visual-verification.spec.ts b/tests/e2e/planning-mode-visual-verification.spec.ts index c51a94f834..ea53f5fc74 100644 --- a/tests/e2e/planning-mode-visual-verification.spec.ts +++ b/tests/e2e/planning-mode-visual-verification.spec.ts @@ -1,4 +1,5 @@ import { expect, test } from "@playwright/test"; +import { mockOnboardingLocalAiConnection } from "./helpers/onboarding-ai-connection"; import { expectLandsOnFirstTaskWithoutDashboardBounce, instrumentNavLog, @@ -35,6 +36,7 @@ test("captures planning mode UI for desktop and mobile", async ({ page }) => { const screenshotDir = "test-results/planning-mode"; await instrumentNavLog(page); + await mockOnboardingLocalAiConnection(page); await page.route("**/test-environment", (route) => route.fulfill({ @@ -92,8 +94,8 @@ test("captures planning mode UI for desktop and mobile", async ({ page }) => { // The connect step arrives with no source selected — the tile row is a // question, not a confirmation — so its CTA stays disabled until one is // pressed. It reads "Connect", not "Next": the button starts the sign-in - // where there is one to start. This instance has no sandbox environment, so - // there is none, and Connect goes straight to the hire. + // where there is one to start. The test simulates successful local account + // connection, then exercises the real first-task creation flow. // // Waited on for enabled rather than visible: it is already on screen, and // clicking a disabled button raises nothing and does nothing. diff --git a/tests/storybook-visual/imessage-photon.config.ts b/tests/storybook-visual/imessage-photon.config.ts new file mode 100644 index 0000000000..aaac3e55ce --- /dev/null +++ b/tests/storybook-visual/imessage-photon.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from "@playwright/test"; + +export default defineConfig({ + testDir: ".", testMatch: "imessage-photon.spec.ts", workers: 2, + timeout: 30_000, retries: 0, outputDir: "./test-results/imessage-photon", + reporter: [["list"]], + use: { browserName: "chromium", baseURL: "http://127.0.0.1:6128", reducedMotion: "reduce" }, + webServer: { + command: "node ../../scripts/serve-storybook-static.mjs --port 6128", + url: "http://127.0.0.1:6128/index.json", reuseExistingServer: false, + }, +}); diff --git a/tests/storybook-visual/imessage-photon.spec.ts b/tests/storybook-visual/imessage-photon.spec.ts new file mode 100644 index 0000000000..a01f28cb6e --- /dev/null +++ b/tests/storybook-visual/imessage-photon.spec.ts @@ -0,0 +1,39 @@ +import { readFileSync } from "node:fs"; +import { resolve } from "node:path"; +import { test, expect } from "@playwright/test"; + +const index = JSON.parse(readFileSync(resolve(import.meta.dirname, "../../ui/storybook-static/index.json"), "utf8")); +type StoryEntry = { id: string; name: string; type: string }; +const stories = (Object.values(index.entries) as StoryEntry[]).filter((entry) => entry.type === "story" && entry.id.startsWith("connections-imessage-photon--")); +const requiredStories = [ + "catalog", "choose-agent", "credentials", "inspecting", "connecting", + "multiple-dedicated-numbers", "no-eligible-line", "provider-outage-recovery", + "reconnect", "access", "incoming-follow-ups", "shared-dm-walkthrough", "narrow-mobile", +]; +const discovered = new Set(stories.map((story) => story.id)); +const missing = requiredStories.filter((name) => !discovered.has(`connections-imessage-photon--${name}`)); +if (missing.length) throw new Error(`Missing required iMessage Photon stories: ${missing.join(", ")}. Rebuild Storybook and check story discovery.`); +for (const story of stories) for (const theme of ["light", "dark"]) { + test(`${story.name} / ${theme}`, async ({ page }, info) => { + const errors: string[] = []; + page.on("pageerror", (error) => errors.push(error.message)); + await page.setViewportSize({ width: /Narrow/.test(story.name) ? 390 : 1200, height: 844 }); + await page.goto(`/iframe.html?id=${story.id}&viewMode=story&globals=theme:${theme}`); + await page.waitForFunction((id) => document.body.dataset.photonStoryReady === id || document.body.dataset.photonStoryError, story.id); + expect(await page.locator("body").getAttribute("data-photon-story-error")).toBeNull(); + await expect(page.locator(".sb-errordisplay")).not.toBeVisible(); + await expect(page.getByText("Simulated Photon demo.", { exact: true })).toBeVisible(); + if (/incoming-follow-ups|narrow-mobile/.test(story.id)) { + await expect(page.getByText("Sent from iMessage", { exact: false })).toHaveCount(2); + } + if (story.id.endsWith("shared-dm-walkthrough")) { + await expect(page.getByRole("heading", { name: "DEMO-1 · Launch plan" })).toBeVisible(); + await expect(page.getByText("Sent from iMessage", { exact: false })).toBeVisible(); + } + if (story.id.endsWith("multiple-dedicated-numbers")) { + await expect(page.getByRole("radio", { name: "+15555550112" })).toBeChecked(); + } + expect(errors).toEqual([]); + await page.screenshot({ path: info.outputPath(`${story.id}-${theme}.png`), fullPage: true, animations: "disabled" }); + }); +} diff --git a/ui/public/brands/apps/LOBE-LICENSE b/ui/public/brands/apps/LOBE-LICENSE new file mode 100644 index 0000000000..1dd53d2a9d --- /dev/null +++ b/ui/public/brands/apps/LOBE-LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 LobeHub + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/ui/public/brands/apps/imessage-photon.png b/ui/public/brands/apps/imessage-photon.png new file mode 100644 index 0000000000..76b0d272e4 Binary files /dev/null and b/ui/public/brands/apps/imessage-photon.png differ diff --git a/ui/public/brands/apps/manifest.json b/ui/public/brands/apps/manifest.json index 0d5af31417..45e49aa069 100644 --- a/ui/public/brands/apps/manifest.json +++ b/ui/public/brands/apps/manifest.json @@ -27,7 +27,7 @@ { "slug": "anthropic", "provider": "Anthropic", - "catalogVisible": false, + "catalogVisible": true, "localAsset": "/brands/apps/anthropic.svg", "darkAsset": "/brands/apps/anthropic-dark.svg", "officialSourceUrl": "https://www.anthropic.com", @@ -221,46 +221,74 @@ "darkVariantRequired": false }, { - "slug": "google-drive", "provider": "Google Drive", "catalogVisible": true, - "localAsset": "/brands/apps/google-drive.svg", "officialSourceUrl": "https://drive.google.com", + "slug": "google-drive", + "provider": "Google Drive", + "catalogVisible": true, + "localAsset": "/brands/apps/google-drive.svg", + "officialSourceUrl": "https://drive.google.com", "upstreamAssetUrl": "https://github.com/simple-icons/simple-icons/blob/16.28.0/icons/googledrive.svg", - "assetType": "svg", "darkVariantRequired": false + "assetType": "svg", + "darkVariantRequired": false }, { - "slug": "google-docs", "provider": "Google Docs", "catalogVisible": true, - "localAsset": "/brands/apps/google-docs.svg", "officialSourceUrl": "https://docs.google.com", + "slug": "google-docs", + "provider": "Google Docs", + "catalogVisible": true, + "localAsset": "/brands/apps/google-docs.svg", + "officialSourceUrl": "https://docs.google.com", "upstreamAssetUrl": "https://github.com/simple-icons/simple-icons/blob/16.28.0/icons/googledocs.svg", - "assetType": "svg", "darkVariantRequired": false + "assetType": "svg", + "darkVariantRequired": false }, { - "slug": "google-slides", "provider": "Google Slides", "catalogVisible": true, - "localAsset": "/brands/apps/google-slides.svg", "officialSourceUrl": "https://slides.google.com", + "slug": "google-slides", + "provider": "Google Slides", + "catalogVisible": true, + "localAsset": "/brands/apps/google-slides.svg", + "officialSourceUrl": "https://slides.google.com", "upstreamAssetUrl": "https://github.com/simple-icons/simple-icons/blob/16.28.0/icons/googleslides.svg", - "assetType": "svg", "darkVariantRequired": false + "assetType": "svg", + "darkVariantRequired": false }, { - "slug": "google-calendar", "provider": "Google Calendar", "catalogVisible": true, - "localAsset": "/brands/apps/google-calendar.svg", "officialSourceUrl": "https://calendar.google.com", + "slug": "google-calendar", + "provider": "Google Calendar", + "catalogVisible": true, + "localAsset": "/brands/apps/google-calendar.svg", + "officialSourceUrl": "https://calendar.google.com", "upstreamAssetUrl": "https://github.com/simple-icons/simple-icons/blob/16.28.0/icons/googlecalendar.svg", - "assetType": "svg", "darkVariantRequired": false + "assetType": "svg", + "darkVariantRequired": false }, { - "slug": "google-chat", "provider": "Google Chat", "catalogVisible": true, - "localAsset": "/brands/apps/google-chat.svg", "officialSourceUrl": "https://chat.google.com", + "slug": "google-chat", + "provider": "Google Chat", + "catalogVisible": true, + "localAsset": "/brands/apps/google-chat.svg", + "officialSourceUrl": "https://chat.google.com", "upstreamAssetUrl": "https://github.com/simple-icons/simple-icons/blob/16.28.0/icons/googlechat.svg", - "assetType": "svg", "darkVariantRequired": false + "assetType": "svg", + "darkVariantRequired": false }, { - "slug": "google-people", "provider": "Google People", "catalogVisible": true, - "localAsset": "/brands/apps/google-people.svg", "officialSourceUrl": "https://contacts.google.com", + "slug": "google-people", + "provider": "Google People", + "catalogVisible": true, + "localAsset": "/brands/apps/google-people.svg", + "officialSourceUrl": "https://contacts.google.com", "upstreamAssetUrl": "https://github.com/simple-icons/simple-icons/blob/16.28.0/icons/googlecontacts.svg", - "assetType": "svg", "darkVariantRequired": false + "assetType": "svg", + "darkVariantRequired": false }, { - "slug": "google-workspace-search", "provider": "Google Workspace Search", "catalogVisible": true, - "localAsset": "/brands/apps/google-workspace-search.svg", "officialSourceUrl": "https://workspace.google.com", + "slug": "google-workspace-search", + "provider": "Google Workspace Search", + "catalogVisible": true, + "localAsset": "/brands/apps/google-workspace-search.svg", + "officialSourceUrl": "https://workspace.google.com", "upstreamAssetUrl": "https://fonts.google.com/icons?selected=Material+Symbols+Outlined:search:FILL@0;wght@400;GRAD@0;opsz@24", - "assetType": "svg", "darkVariantRequired": false + "assetType": "svg", + "darkVariantRequired": false }, { "slug": "hugging-face", @@ -634,6 +662,52 @@ "upstreamAssetUrl": "https://github.com/simple-icons/simple-icons/blob/16.28.0/icons/zapier.svg", "assetType": "svg", "darkVariantRequired": false + }, + { + "slug": "openai", + "provider": "OpenAI", + "catalogVisible": true, + "localAsset": "/brands/apps/openai.svg", + "darkAsset": "/brands/apps/openai-dark.svg", + "officialSourceUrl": "https://openai.com/brand/", + "upstreamAssetUrl": "https://github.com/lobehub/lobe-icons/blob/a94750e3f5f8fc33757b839d85030e742284e43a/packages/static-svg/icons/openai.svg", + "assetType": "svg", + "darkVariantRequired": true, + "verifiedAt": "2026-09-10" + }, + { + "slug": "openrouter", + "provider": "OpenRouter", + "catalogVisible": true, + "localAsset": "/brands/apps/openrouter.svg", + "darkAsset": "/brands/apps/openrouter-dark.svg", + "officialSourceUrl": "https://openrouter.ai/blog/announcements/brand-refresh/", + "upstreamAssetUrl": "https://github.com/OpenRouterTeam/sign-in-with-openrouter/blob/main/public/openrouter-logo-light.svg", + "assetType": "svg", + "darkVariantRequired": true, + "verifiedAt": "2026-09-10" + }, + { + "slug": "xai", + "provider": "Grok", + "catalogVisible": true, + "localAsset": "/brands/apps/xai.svg", + "darkAsset": "/brands/apps/xai-dark.svg", + "officialSourceUrl": "https://x.ai/", + "upstreamAssetUrl": "https://github.com/lobehub/lobe-icons/blob/a94750e3f5f8fc33757b839d85030e742284e43a/packages/static-svg/icons/grok.svg", + "assetType": "svg", + "darkVariantRequired": true, + "verifiedAt": "2026-09-10" + }, + { + "slug": "imessage-photon", + "provider": "iMessage Photon", + "catalogVisible": true, + "localAsset": "/brands/apps/imessage-photon.png", + "officialSourceUrl": "https://photon.codes/", + "upstreamAssetUrl": "https://framerusercontent.com/images/XbFmp7b9ze39qL1GdqVbmJXbiS8.png?height=512&width=512", + "assetType": "png", + "darkVariantRequired": false } ] } diff --git a/ui/public/brands/apps/openai-dark.svg b/ui/public/brands/apps/openai-dark.svg new file mode 100644 index 0000000000..fb4d0ac4d7 --- /dev/null +++ b/ui/public/brands/apps/openai-dark.svg @@ -0,0 +1 @@ +OpenAI \ No newline at end of file diff --git a/ui/public/brands/apps/openai.svg b/ui/public/brands/apps/openai.svg new file mode 100644 index 0000000000..318403d617 --- /dev/null +++ b/ui/public/brands/apps/openai.svg @@ -0,0 +1 @@ +OpenAI \ No newline at end of file diff --git a/ui/public/brands/apps/openrouter-dark.svg b/ui/public/brands/apps/openrouter-dark.svg new file mode 100644 index 0000000000..8b383b37c1 --- /dev/null +++ b/ui/public/brands/apps/openrouter-dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/public/brands/apps/openrouter.svg b/ui/public/brands/apps/openrouter.svg new file mode 100644 index 0000000000..6ac6fcf812 --- /dev/null +++ b/ui/public/brands/apps/openrouter.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/public/brands/apps/xai-dark.svg b/ui/public/brands/apps/xai-dark.svg new file mode 100644 index 0000000000..cb1537fd2d --- /dev/null +++ b/ui/public/brands/apps/xai-dark.svg @@ -0,0 +1 @@ +Grok \ No newline at end of file diff --git a/ui/public/brands/apps/xai.svg b/ui/public/brands/apps/xai.svg new file mode 100644 index 0000000000..a813f6c5cb --- /dev/null +++ b/ui/public/brands/apps/xai.svg @@ -0,0 +1 @@ +Grok \ No newline at end of file diff --git a/ui/src/api/agents.ts b/ui/src/api/agents.ts index c55fe7c625..c25bbabd91 100644 --- a/ui/src/api/agents.ts +++ b/ui/src/api/agents.ts @@ -223,6 +223,7 @@ export const agentsApi = { type: string, data: { adapterConfig: Record; + aiConnection?: import("@paperclipai/shared").AiConnectionBinding; agentId?: string; testCredentials?: Record; environmentId?: string | null; @@ -274,7 +275,7 @@ export const agentsApi = { startAdapterAuthLogin: ( companyId: string, type: string, - data: { environmentId: string; ttlSeconds?: number }, + data: { environmentId: string; ttlSeconds?: number; aiConnection?: import("@paperclipai/shared").AiConnectionLoginIntent }, ) => api.post( `/companies/${encodeURIComponent(companyId)}/adapters/${encodeURIComponent(type)}/login-sessions`, @@ -317,7 +318,7 @@ export const agentsApi = { ), startClaudeSetupTokenLogin: ( companyId: string, - data: { environmentId: string; overwrite?: ClaudeSetupTokenOverwrite }, + data: { environmentId: string; overwrite?: ClaudeSetupTokenOverwrite; aiConnection?: import("@paperclipai/shared").AiConnectionLoginIntent }, ) => api.post( `/companies/${encodeURIComponent(companyId)}/setup-token-login-sessions`, diff --git a/ui/src/api/ai-connections.ts b/ui/src/api/ai-connections.ts new file mode 100644 index 0000000000..c9793205c2 --- /dev/null +++ b/ui/src/api/ai-connections.ts @@ -0,0 +1,13 @@ +import type { AiManagedConnectionSummary, CreateAiConnection, AiConnectionLoginIntent, LocalAiLoginAttempt, LocalAiLoginStatus } from "@paperclipai/shared"; +import { api } from "./client"; +export const aiConnectionsApi = { + startLocalLogin: (companyId: string, input: AiConnectionLoginIntent & { restart?: boolean }) => api.post(`/companies/${companyId}/ai-connections/local/attempts`, input), + checkLocalLogin: (companyId: string, input: AiConnectionLoginIntent & { localSessionId?: string }) => api.post(`/companies/${companyId}/ai-connections/local/check`, input), + cancelLocalLogin: (companyId: string, sessionId: string) => api.delete(`/companies/${companyId}/ai-connections/local/attempts/${sessionId}`), + connectLocal: (companyId: string, input: AiConnectionLoginIntent & { localSessionId?: string }) => api.post<{ connectionId: string; grantId: string }>(`/companies/${companyId}/ai-connections/local`, input), + activeRuns: (companyId: string, connectionId: string) => api.get>(`/companies/${companyId}/ai-connections/${connectionId}/active-runs`), + list: (companyId: string, agentId?: string) => api.get<{ currentUserId: string; connections: AiManagedConnectionSummary[] }>(`/companies/${companyId}/ai-connections${agentId ? `?agentId=${encodeURIComponent(agentId)}` : ""}`), + create: (companyId: string, input: CreateAiConnection) => api.post<{ connectionId: string; grantId: string }>(`/companies/${companyId}/ai-connections`, input), + setDefault: (companyId: string, grantId: string) => api.put(`/companies/${companyId}/ai-connections/default`, { grantId }), + loginResult: (companyId: string, sessionId: string) => api.get<{ connectionId: string; grantId: string }>(`/companies/${companyId}/ai-connections/login/${encodeURIComponent(sessionId)}`), +}; diff --git a/ui/src/api/chatEndpoints.ts b/ui/src/api/chatEndpoints.ts index 1072e455d2..43d9ab629c 100644 --- a/ui/src/api/chatEndpoints.ts +++ b/ui/src/api/chatEndpoints.ts @@ -1,5 +1,7 @@ import { api } from "./client"; import type { + PhotonProjectInspection, + PhotonChannelConfiguration, ChatPublicationBatchStatus, ChatPublicationState, ChatPublicationSummary, @@ -12,7 +14,7 @@ export type { } from "@paperclipai/shared"; export type ChatProvider = - "slack" | "github" | "discord" | "microsoft-teams" | "telegram" | "agentmail"; + "slack" | "github" | "discord" | "microsoft-teams" | "telegram" | "agentmail" | "imessage-photon"; export type ChatEndpointStatus = | "draft" | "verifying" @@ -33,6 +35,7 @@ export interface ChatEndpointResource { availability: "available" | "unavailable" | "removed"; enabled: boolean; detail?: string | null; + participants?: string[]; } export interface ChatIdentityLink { @@ -97,6 +100,7 @@ export interface ChatEndpoint { botLabel?: string | null; botUsername?: string | null; botExternalId?: string | null; + photonAllocation?: "dedicated" | "shared"; allowDirectMessages?: boolean; allowGroupChats?: boolean; allowUnlinkedPeople: boolean; @@ -188,8 +192,11 @@ export const chatEndpointsApi = { input: { action: ChatEndpointSetupAction; credentials?: Record; + photon?: PhotonChannelConfiguration; }, ) => api.post(`/chat-endpoints/${endpointId}/setup`, input), + inspectPhoton: (endpointId: string, input: { projectId: string; projectSecret: string }) => + api.post(`/chat-endpoints/${endpointId}/photon/inspect`, input), generateSetupSecret: (endpointId: string) => api.post( `/chat-endpoints/${endpointId}/setup-secret`, diff --git a/ui/src/api/health.ts b/ui/src/api/health.ts index 8068d2614a..ac8cbc00a2 100644 --- a/ui/src/api/health.ts +++ b/ui/src/api/health.ts @@ -28,6 +28,7 @@ export type HealthStatus = { version?: string; deploymentMode?: "local_trusted" | "authenticated"; deploymentExposure?: "private" | "public"; + localAiLoginSupported?: boolean; authReady?: boolean; bootstrapStatus?: "ready" | "bootstrap_pending"; bootstrapInviteActive?: boolean; diff --git a/ui/src/api/issues.test.ts b/ui/src/api/issues.test.ts index 3d78406b57..31a4548767 100644 --- a/ui/src/api/issues.test.ts +++ b/ui/src/api/issues.test.ts @@ -29,6 +29,23 @@ describe("issuesApi.list", () => { mockApi.patch.mockResolvedValue({}); }); + it.each([null, "stopped-run"])("dispatches a stopped queue using its current revision (%s)", async (target) => { + mockApi.get.mockResolvedValueOnce({ queueId: "queue-1", targetRunId: null, revision: "revision-2" }); + await issuesApi.interruptLatestQueuedComments("issue-1", target); + expect(mockApi.post).toHaveBeenCalledWith("/issues/issue-1/queued-comments/interrupt", { + queueId: "queue-1", targetRunId: null, revision: "revision-2", + }); + }); + + it.each([ + { queueId: null, targetRunId: null, revision: "empty" }, + { queueId: "queue-1", targetRunId: "new-run", revision: "changed" }, + ])("rejects a changed or empty queue before interruption", async queue => { + mockApi.get.mockResolvedValueOnce(queue); + await expect(issuesApi.interruptLatestQueuedComments("issue-1", "old-run")).rejects.toThrow("queued messages changed"); + expect(mockApi.post).not.toHaveBeenCalled(); + }); + it("fetches all pages of tasks created from the source without filtering parentage", async () => { const firstPage = Array.from({ length: 500 }, (_, index) => ({ id: `task-${index}` })); mockApi.get.mockResolvedValueOnce(firstPage).mockResolvedValueOnce([{ id: "last-task" }]); diff --git a/ui/src/api/issues.ts b/ui/src/api/issues.ts index 9abac76847..7d8bf562a3 100644 --- a/ui/src/api/issues.ts +++ b/ui/src/api/issues.ts @@ -386,8 +386,17 @@ export const issuesApi = { ), interruptQueuedComments: ( id: string, - data: { queueId: string; targetRunId: string; revision: string }, + data: { queueId: string; targetRunId: string | null; revision: string }, ) => api.post(`/issues/${id}/queued-comments/interrupt`, data), + interruptLatestQueuedComments: async (id: string, expectedTargetRunId: string | null): Promise => { + const queue = await issuesApi.getQueuedComments(id); + if (!queue.queueId || (queue.targetRunId && queue.targetRunId !== expectedTargetRunId)) { + throw new Error("The queued messages changed. Refresh and try again."); + } + return issuesApi.interruptQueuedComments(id, { + queueId: queue.queueId, revision: queue.revision, targetRunId: queue.targetRunId, + }); + }, steerQueuedComment: ( id: string, commentId: string, diff --git a/ui/src/components/AdapterLoginChrome.tsx b/ui/src/components/AdapterLoginChrome.tsx index a8028cf950..6d7aa7e63e 100644 --- a/ui/src/components/AdapterLoginChrome.tsx +++ b/ui/src/components/AdapterLoginChrome.tsx @@ -49,6 +49,7 @@ export type AdapterLoginChrome = "panel" | "onboarding"; export const CONNECT_SOURCE_NAMES: Record = { claude_local: "Claude", codex_local: "OpenAI", + grok_local: "Grok", }; /** The provider name for a source, falling back to the type when unlisted. */ @@ -170,9 +171,12 @@ function LoginCardCopyButton({ const [copied, setCopied] = useState(false); const timeoutRef = useRef | null>(null); - useEffect(() => () => { - if (timeoutRef.current) clearTimeout(timeoutRef.current); - }, []); + useEffect( + () => () => { + if (timeoutRef.current) clearTimeout(timeoutRef.current); + }, + [], + ); return ( } + :

{isolated ? `Sign in to ${provider} for this connection on the machine running Paperclip. Your existing terminal login stays separate.` : `Connect uses your local ${provider} account on the machine running Paperclip.`}

} + {(!ready || showCommand) && !login?.error && <> +

Run this in a terminal on that machine and finish signing in in your browser. We’ll check automatically when you return.

+ {command &&
+
{command}
+ +
} + } + {login?.error &&

{login.error}

} + {login && !login.preparing && (isolated || login.error) && } + ; +} diff --git a/ui/src/components/AgentConfigForm.tsx b/ui/src/components/AgentConfigForm.tsx index 91534ffb16..fb4e0b4d12 100644 --- a/ui/src/components/AgentConfigForm.tsx +++ b/ui/src/components/AgentConfigForm.tsx @@ -1,3 +1,5 @@ +import { AiConnectionField } from "./ai-connections/AiConnectionField"; +import { aiConnectionBindingSchema } from "@paperclipai/shared"; import { testAgentSetup } from "@/lib/test-agent-setup"; import { RuntimeTestCard } from "./RuntimeTestCard"; import { useState, useEffect, useRef, useMemo, useCallback, Children, isValidElement, type ReactNode } from "react"; @@ -44,7 +46,7 @@ import { asBoolean, asFiniteNumber, asObject, cn } from "../lib/utils"; import { copyTextToClipboard } from "../lib/clipboard"; import { connectSourceName, - OnboardingLoginCard, + ProviderSubscriptionCard, OnboardingCardField, OnboardingLoginCodeRow, type AdapterLoginChrome, @@ -888,9 +890,12 @@ export function AgentConfigForm(props: AgentConfigFormProps) { ? String(isCreate ? props.values.adapterSchemaValues?.provider ?? "codex" : eff("adapterConfig", "provider", config.provider === "acpx" && config.acpxAgent === "codex" ? "codex" : config.provider ?? "codex")) : undefined; + const modelProvider = adapterType === "opencode_local" && aiConnectionBindingSchema.safeParse( + (overlay.runtime.runtimeConfig as Record | undefined)?.aiConnection ?? runtimeConfig.aiConnection, + ).data?.provider === "openrouter" ? "openrouter" : runnerProvider; // Fetch adapter models for the effective provider, including unsaved changes. const modelQueryKey = selectedCompanyId - ? queryKeys.agents.adapterModels(selectedCompanyId, adapterType, currentDefaultEnvironmentId || null, runnerProvider) + ? queryKeys.agents.adapterModels(selectedCompanyId, adapterType, currentDefaultEnvironmentId || null, modelProvider) : ["agents", "none", "adapter-models", adapterType]; const { data: fetchedModels, @@ -899,7 +904,7 @@ export function AgentConfigForm(props: AgentConfigFormProps) { queryKey: modelQueryKey, queryFn: () => agentsApi.adapterModels(selectedCompanyId!, adapterType, { environmentId: currentDefaultEnvironmentId || null, - provider: runnerProvider, + provider: modelProvider, }), enabled: Boolean(selectedCompanyId), }); @@ -1055,15 +1060,18 @@ export function AgentConfigForm(props: AgentConfigFormProps) { }); const adapterConfig = buildAdapterConfigForTest(adapterConfigPatch); const agentId = isCreate ? undefined : props.agent.id; + const aiConnection = isCreate ? undefined : aiConnectionBindingSchema.safeParse( + (overlay.runtime.runtimeConfig as Record | undefined)?.aiConnection ?? props.agent.runtimeConfig.aiConnection, + ).data; if (props.compactTestFeedback) { const providerAdapter = adapterType === "paperclip_runner" ? adapterConfig.provider === "codex" ? "codex_local" : adapterConfig.provider === "acpx" && adapterConfig.acpxAgent === "claude" ? "claude_local" : adapterType : adapterType; - return testAgentSetup({ companyId: selectedCompanyId, adapterType, providerAdapter, adapterConfig, agentId, environmentId }); + return testAgentSetup({ companyId: selectedCompanyId, adapterType, providerAdapter, adapterConfig, agentId, aiConnection, environmentId }); } - return agentsApi.testEnvironment(selectedCompanyId, adapterType, { adapterConfig, agentId, environmentId }); + return agentsApi.testEnvironment(selectedCompanyId, adapterType, { adapterConfig, agentId, aiConnection, environmentId }); }, }); const [testActionPending, setTestActionPending] = useState(false); @@ -1139,6 +1147,7 @@ export function AgentConfigForm(props: AgentConfigFormProps) { environmentCapabilities?.sandboxProviders?.[effectiveLoginProvider]?.supportsLoginPty === true; const loginNeedsPty = adapterCaps.login != null; const showAdapterLogin = + (isCreate || !((overlay.runtime.runtimeConfig as Record | undefined)?.aiConnection ?? runtimeConfig.aiConnection)) && adapterSupportsSandboxLogin && effectiveLoginEnvironment?.driver === "sandbox" && Boolean(effectiveLoginEnvironmentId) && @@ -1243,7 +1252,7 @@ export function AgentConfigForm(props: AgentConfigFormProps) { setRefreshingModels(true); setRefreshModelsError(null); try { - const refreshed = await agentsApi.adapterModels(selectedCompanyId, adapterType, { refresh: true, environmentId: currentDefaultEnvironmentId || null, provider: runnerProvider }); + const refreshed = await agentsApi.adapterModels(selectedCompanyId, adapterType, { refresh: true, environmentId: currentDefaultEnvironmentId || null, provider: modelProvider }); queryClient.setQueryData(modelQueryKey, refreshed); } catch (error) { setRefreshModelsError(error instanceof Error ? error.message : "Failed to refresh adapter models."); @@ -1641,6 +1650,11 @@ export function AgentConfigForm(props: AgentConfigFormProps) { )} + {!isCreate && selectedCompanyId && | undefined)?.aiConnection ?? runtimeConfig.aiConnection).data} + model={String(eff("adapterConfig", "model", config.model) ?? "")} environmentId={currentDefaultEnvironmentId || undefined} legacy + onChange={binding => mark("runtime", "runtimeConfig", { ...runtimeConfig, aiConnection: binding })} />} + {showInlineAdapterTestEnvironmentFeedback && !props.compactTestFeedback && (testActionError || testEnvironment.error) && (
{testActionError @@ -2243,6 +2257,7 @@ export type AdapterLoginDescriptor = { // correctly, and the first thing to rot would have been the timeout and // cleanup paths, which are the ones nobody exercises by hand. export type AdapterLoginPanelProps = AdapterLoginDescriptor & { + aiConnection?: import("@paperclipai/shared").AiConnectionLoginIntent; onStored?: (storedSessionId: string) => void; onApplyStored?: () => void; // Applies the non-secret Codex account-binding claim from an authenticated @@ -2261,7 +2276,7 @@ export type AdapterLoginPanelProps = AdapterLoginDescriptor & { // The login reached its success state. Onboarding advances on this, which is // why the `onboarding` chrome draws no success state of its own — the screen // it would appear on is already gone. - onConnected?: () => void; + onConnected?: (sessionId?: string) => void; // The pasted code went to the server. Fires as the submit starts rather than // when the login finishes, so a caller can show the work the moment the // customer has done their part: the round trip to `onConnected` is a poll @@ -2326,6 +2341,7 @@ function DisplayedCodeLoginPanel({ onConnected, onAccountBinding, chrome = "panel", + aiConnection, onPromptReady, }: AdapterLoginPanelProps) { const [sessionId, setSessionId] = useState(null); @@ -2350,7 +2366,7 @@ function DisplayedCodeLoginPanel({ const resumedRef = useRef(false); const startLogin = useMutation({ - mutationFn: () => agentsApi.startAdapterAuthLogin(companyId, adapterType, { environmentId }), + mutationFn: () => agentsApi.startAdapterAuthLogin(companyId, adapterType, { environmentId, aiConnection }), onSuccess: (session) => { resumedRef.current = false; setStartError(null); @@ -2389,7 +2405,10 @@ function DisplayedCodeLoginPanel({ queryKey: ["adapter-login-active-session", companyId, adapterType], queryFn: async () => { try { - return await agentsApi.getActiveAdapterAuthLoginSession(companyId, adapterType); + const active = await agentsApi.getActiveAdapterAuthLoginSession(companyId, adapterType); + if (!active) return null; + if ((aiConnection && active.environmentId !== environmentId) || Boolean(active.aiConnection) !== Boolean(aiConnection) || (aiConnection && (active.aiConnection?.provider !== aiConnection.provider || active.aiConnection?.method !== aiConnection.method || active.aiConnection?.connectionId !== aiConnection.connectionId || active.aiConnection?.ownership !== aiConnection.ownership || active.aiConnection?.allAgents !== aiConnection.allAgents || JSON.stringify(active.aiConnection?.agentIds) !== JSON.stringify(aiConnection.agentIds)))) throw new Error("Another sign-in attempt is active. Finish or cancel it in its original account setup before starting this one."); + return active; } catch (error) { if (error instanceof ApiError && error.status === 404) return null; throw error; @@ -2531,7 +2550,7 @@ function DisplayedCodeLoginPanel({ useEffect(() => { if (status !== "authenticated" || connectedRef.current) return; connectedRef.current = true; - onConnectedRef.current?.(); + onConnectedRef.current?.(sessionId ?? undefined); }, [status]); // Drive the account-binding hand-off as a visible state machine, not a @@ -2583,24 +2602,11 @@ function DisplayedCodeLoginPanel({ if (chrome === "onboarding") { const failed = isTerminal && status && status !== "authenticated"; return ( - - {/* The same destination as the step's own button. Two ways to one - link: the button for the customer following the flow, the anchor - for anyone finishing in another browser. */} - - Sign in to {connectSourceName(adapterType)} - - {" by providing the authorization code below"} - - } + providerName={connectSourceName(adapterType)} + authorizationUrl={prompt?.url} + mode="displayed_code" > {startError ? (

@@ -2617,7 +2623,7 @@ function DisplayedCodeLoginPanel({ ) : ( )} - + ); } @@ -2817,6 +2823,7 @@ function SubmittedBrowserCodeLoginPanel({ onCodeSubmitted, onSubmitFailed, chrome = "panel", + aiConnection, onPromptReady, }: AdapterLoginPanelProps) { const [sessionId, setSessionId] = useState(null); @@ -2904,10 +2911,11 @@ function SubmittedBrowserCodeLoginPanel({ mutationFn: () => agentsApi.startClaudeSetupTokenLogin(companyId, { environmentId, + aiConnection, // When the owner already has a stored token, the login rotates it under // the captured version, so a replacement login never conflicts with an // existing value. Without a stored token the login is a first write. - ...(storedToken + ...(storedToken && !aiConnection ? { overwrite: { expectedSecretId: storedToken.secretId, @@ -2981,7 +2989,10 @@ function SubmittedBrowserCodeLoginPanel({ queryKey: ["claude-setup-token-active-session", companyId], queryFn: async () => { try { - return await agentsApi.getActiveClaudeSetupTokenLoginSession(companyId); + const active = await agentsApi.getActiveClaudeSetupTokenLoginSession(companyId); + if (!active) return null; + if ((aiConnection && active.environmentId !== environmentId) || Boolean(active.aiConnection) !== Boolean(aiConnection) || (aiConnection && (active.aiConnection?.provider !== aiConnection.provider || active.aiConnection?.method !== aiConnection.method || active.aiConnection?.connectionId !== aiConnection.connectionId || active.aiConnection?.ownership !== aiConnection.ownership || active.aiConnection?.allAgents !== aiConnection.allAgents || JSON.stringify(active.aiConnection?.agentIds) !== JSON.stringify(aiConnection.agentIds)))) throw new Error("Another sign-in attempt is active. Finish or cancel it in its original account setup before starting this one."); + return active; } catch (error) { if (error instanceof ApiError && error.status === 404) return null; throw error; @@ -3301,7 +3312,7 @@ function SubmittedBrowserCodeLoginPanel({ useEffect(() => { if (!isStored || connectedRef.current) return; connectedRef.current = true; - onConnectedRef.current?.(); + onConnectedRef.current?.(sessionId ?? undefined); }, [isStored]); // The other end of `onCodeSubmitted`. Any of these after a submit means the @@ -3325,21 +3336,11 @@ function SubmittedBrowserCodeLoginPanel({ if (chrome === "onboarding") { const failedNow = isFailure || timedOut; return ( - - - Sign in to {connectSourceName(adapterType)} - - {" then come back and enter authorization code"} - - } + providerName={connectSourceName(adapterType)} + authorizationUrl={authorizationUrl ?? undefined} + mode="submitted_code" > {/* The plain-HTTP advisory survives the redesign. It is the one thing on this card not about getting the login done, and dropping it to keep @@ -3375,7 +3376,7 @@ function SubmittedBrowserCodeLoginPanel({ disabled={submitCode.isPending || isCompleting || codeSubmitted} /> )} - + ); } diff --git a/ui/src/components/CommentThread.tsx b/ui/src/components/CommentThread.tsx index 0557c37ee9..6f26b23ae9 100644 --- a/ui/src/components/CommentThread.tsx +++ b/ui/src/components/CommentThread.tsx @@ -103,7 +103,7 @@ interface CommentThreadProps { currentAssigneeValue?: string; suggestedAssigneeValue?: string; mentions?: MentionOption[]; - onInterruptQueued?: (runId: string) => Promise; + onInterruptQueued?: (runId: string | null) => Promise; interruptingQueuedRunId?: string | null; composerDisabledReason?: string | null; externalReferences?: MarkdownExternalReferenceMap; diff --git a/ui/src/components/InlineEntitySelector.test.tsx b/ui/src/components/InlineEntitySelector.test.tsx index 668cb799eb..1ae6f681bd 100644 --- a/ui/src/components/InlineEntitySelector.test.tsx +++ b/ui/src/components/InlineEntitySelector.test.tsx @@ -129,6 +129,8 @@ describe("InlineEntitySelector", () => { const searchInput = document.querySelector('input[placeholder="Search responsible..."]') as HTMLInputElement | null; expect(searchInput).not.toBeNull(); + expect(searchInput?.className).toContain("text-base"); + expect(document.querySelector("[data-mobile-entity-picker]")).not.toBeNull(); expect(document.activeElement).toBe(searchInput); act(() => { diff --git a/ui/src/components/InlineEntitySelector.tsx b/ui/src/components/InlineEntitySelector.tsx index c251b7ac89..9b3b857f89 100644 --- a/ui/src/components/InlineEntitySelector.tsx +++ b/ui/src/components/InlineEntitySelector.tsx @@ -142,6 +142,7 @@ export const InlineEntitySelector = forwardRef { diff --git a/ui/src/components/IssueChatThread.test.tsx b/ui/src/components/IssueChatThread.test.tsx index c6392711ea..23ec7af208 100644 --- a/ui/src/components/IssueChatThread.test.tsx +++ b/ui/src/components/IssueChatThread.test.tsx @@ -426,6 +426,45 @@ describe("IssueChatThread", () => { }); }); + it("labels incoming iMessage bubbles without labeling board replies", () => { + const root = createRoot(container); + act(() => { + root.render( + + ({ + id: `comment-${source}`, + companyId: "company-1", + issueId: "issue-1", + authorAgentId: null, + authorUserId: "user-board", + authorType: "user" as const, + body: `Reply from ${source}`, + presentation: null, + metadata: source === "imessage" ? { + version: 1 as const, + sourceChannel: "imessage-photon" as const, + sections: [{ title: "iMessage Photon sender", rows: [{ type: "text" as const, text: "Linked person" }] }], + } : null, + createdAt: new Date("2026-09-12T12:00:00Z"), + updatedAt: new Date("2026-09-12T12:00:00Z"), + }))} + linkedRuns={[]} + timelineEvents={[]} + liveRuns={[]} + currentUserId="user-board" + onAdd={async () => {}} + showComposer={false} + enableLiveTranscriptPolling={false} + /> + , + ); + }); + expect(container.querySelector("#comment-comment-imessage")?.textContent).toContain("Sent from iMessage"); + expect(container.querySelector("#comment-comment-board")?.textContent).not.toContain("Sent from iMessage"); + act(() => root.unmount()); + }); + it("uses accent-safe markdown color in the current user's blue message bubble", () => { const root = createRoot(container); @@ -3111,6 +3150,24 @@ describe("IssueChatThread", () => { act(() => root.unmount()); }); + it("dispatches queued messages with Interrupt after the target run has stopped", () => { + const root = createRoot(container); + const onInterruptQueued = vi.fn(async () => {}); + act(() => root.render( {}} onInterruptQueued={onInterruptQueued} showComposer={false} + enableLiveTranscriptPolling={false} + />)); + const interrupt = [...container.querySelectorAll("button")].find(button => button.textContent === "Interrupt"); + expect(interrupt).toBeDefined(); + act(() => interrupt!.click()); + expect(onInterruptQueued).toHaveBeenCalledWith(null); + act(() => root.unmount()); + }); + it("shows deferred wake badge only for hold-deferred queued comments", () => { const root = createRoot(container); diff --git a/ui/src/components/IssueChatThread.tsx b/ui/src/components/IssueChatThread.tsx index 49c49b34df..cb398cbbb3 100644 --- a/ui/src/components/IssueChatThread.tsx +++ b/ui/src/components/IssueChatThread.tsx @@ -271,7 +271,7 @@ interface IssueChatMessageContext { stoppingRunLabel?: string; stopRunVariant?: "stop" | "pause"; runFinalizationActions?: readonly IssueChatRunFinalizationAction[]; - onInterruptQueued?: (runId: string) => Promise; + onInterruptQueued?: (runId: string | null) => Promise; onCancelQueued?: (commentId: string) => void; onDeleteComment?: (commentId: string) => Promise | void; onImageClick?: (src: string) => void; @@ -649,7 +649,7 @@ interface IssueChatThreadProps { transcriptsByRunId?: ReadonlyMap; hasOutputForRun?: (runId: string) => boolean; includeSucceededRunsWithoutOutput?: boolean; - onInterruptQueued?: (runId: string) => Promise; + onInterruptQueued?: (runId: string | null) => Promise; onCancelQueued?: (commentId: string) => void; /** Authoritative PRP queue. The classic thread intentionally ignores it. */ queuedCommentQueue?: IssueQueuedCommentQueue | null; @@ -2019,6 +2019,8 @@ function IssueChatUserMessage({ ? custom.sourceTrust : null; const followUpRequested = custom.followUpRequested === true; + const sentFromIMessage = isIssueCommentMetadata(custom.commentMetadata) && + custom.commentMetadata.sourceChannel === "imessage-photon"; const queueReason = typeof custom.queueReason === "string" ? custom.queueReason : null; const queueBadgeLabel = @@ -2114,7 +2116,7 @@ function IssueChatUserMessage({ > {queueBadgeLabel} - {queueTargetRunId && onInterruptQueued ? ( + {onInterruptQueued ? ( { expect(occurrenceCount(queuedComment.body)).toBe(1); }); - it("keeps legacy follow-ups in the composer queue with an interrupt fallback", () => { + it.each(["run-1", null])("keeps legacy queued delivery available with target %s", (targetRunId) => { const onInterruptQueued = vi.fn(async () => {}); render( { onInterruptQueued={onInterruptQueued} queuedCommentQueue={{ ...queue, + targetRunId, protocol: "legacy", steeringDisposition: "unsupported", }} @@ -2929,7 +2930,7 @@ describe("TaskChatThread Paperclip Runner queue", () => { ); expect(interrupt).not.toBeNull(); flushSync(() => interrupt!.click()); - expect(onInterruptQueued).toHaveBeenCalledWith("run-1"); + expect(onInterruptQueued).toHaveBeenCalledWith(targetRunId); }); it("cancels an optimistic queued row locally before server acknowledgement", async () => { diff --git a/ui/src/components/TaskChatThread.tsx b/ui/src/components/TaskChatThread.tsx index 45e95fe937..ab6c297500 100644 --- a/ui/src/components/TaskChatThread.tsx +++ b/ui/src/components/TaskChatThread.tsx @@ -96,7 +96,7 @@ import { cn } from "@/lib/utils"; import { Skeleton } from "@/components/ui/skeleton"; import { Button } from "@/components/ui/button"; import { useIssuePlanDocument } from "@/hooks/useIssuePlanDocument"; -import { latestSameRunHandoffTimestamp } from "@/lib/issue-chat-messages"; +import { isRedundantAiRecoveryNotice, latestSameRunHandoffTimestamp } from "@/lib/issue-chat-messages"; import { isLiveIssueRun, isTerminalIssueStatus } from "@/lib/liveIssueIds"; import { resolveTaskChatBlockers, @@ -762,6 +762,7 @@ export function TaskChatThread(props: TaskChatThreadProps) { const projectedComments = useMemo( () => comments.flatMap((comment) => { + if (isRedundantAiRecoveryNotice(comment, interactions)) return []; if (comment.body !== LEGACY_WITHHELD_RUN_COMMENT || !comment.runId) return [comment]; const resultJson = linkedRunMetaById.get(comment.runId)?.resultJson; @@ -774,7 +775,7 @@ export function TaskChatThread(props: TaskChatThreadProps) { const summary = acceptedSemanticResultSummary(resultJson); return [summary ? { ...comment, body: summary } : comment]; }), - [comments, linkedRunMetaById], + [comments, interactions, linkedRunMetaById], ); const commentItems = useMemo( @@ -1632,8 +1633,12 @@ export function TaskChatThread(props: TaskChatThreadProps) { const retryDetail = meta?.scheduledRetryAt ? "Retry scheduled automatically." : "You can retry this message now."; - const detail = - source.status === "cancelled" + const aiRequest = interactions?.find((interaction) => interaction.kind === "connection_intent" && interaction.payload.purpose === "ai" && interaction.sourceRunId === source.id); + const detail = aiRequest + ? aiRequest.status === "pending" + ? "The selected AI account is unavailable. Fix it in the connection card." + : "This run stopped because its AI account was unavailable." + : source.status === "cancelled" ? code === "execution_reconciliation_required" ? "The previous execution must be checked before this task can continue. Your message is preserved. View the stopped run for details." : "Execution was stopped before returning an answer." @@ -1970,6 +1975,7 @@ export function TaskChatThread(props: TaskChatThreadProps) { }; }, [ orderedEntries, + interactions, runs, liveRun, transcriptByRun, @@ -2858,7 +2864,9 @@ export function TaskChatThread(props: TaskChatThreadProps) { : (liveRun && liveRun.id === tailRunId ? liveRun.currentStatusMessage : null) || - "Waiting for transcript..." + (tailStatus === "failed" + ? "This run stopped before a response was available. Review the task’s connection or recovery action below." + : "Waiting for transcript...") } /> @@ -2935,10 +2943,10 @@ export function TaskChatThread(props: TaskChatThreadProps) { await onSteerQueuedComment(commentId, revision); }} onInterrupt={ - onInterruptQueued && queuedMessageQueue.targetRunId + onInterruptQueued && queuedMessageQueue.queueId ? async () => { await onInterruptQueued( - queuedMessageQueue.targetRunId!, + queuedMessageQueue.targetRunId, ); } : undefined diff --git a/ui/src/components/ai-connections/AiConnectionAccountControls.tsx b/ui/src/components/ai-connections/AiConnectionAccountControls.tsx new file mode 100644 index 0000000000..f4bd58bbe9 --- /dev/null +++ b/ui/src/components/ai-connections/AiConnectionAccountControls.tsx @@ -0,0 +1,69 @@ +import { useState, type ReactNode } from "react"; +import { CheckCircle2, RefreshCw, Star, TriangleAlert, Unplug } from "lucide-react"; +import type { ConnectionGrant } from "@paperclipai/shared"; +import { RevokeGrantDialog } from "@/pages/apps/app-detail/IdentitiesSection"; +import { Button } from "@/components/ui/button"; +import { cn } from "@/lib/utils"; +import { aiMethodLabel, type AiConnectionSummary } from "./model"; + +/** AI-only account controls; identity, access and navigation belong to AppDetail. */ +export function AiConnectionAccountControls({ + account, grant, currentUserId, readOnly, onMakeDefault, onReconnect, onRevoke, revocationDetails, +}: { + account: AiConnectionSummary; + grant: ConnectionGrant; + currentUserId: string; + readOnly?: boolean; + onMakeDefault: () => void; + onReconnect: () => void; + onRevoke: () => void | Promise; + revocationDetails?: ReactNode; +}) { + const [revoking, setRevoking] = useState(false); + const [revokePending, setRevokePending] = useState(false); + const [revokeError, setRevokeError] = useState(); + const ownPersonal = account.ownership === "personal" && account.ownerUserId === currentUserId; + const available = account.status === "connected"; + const activeDefault = account.isDefault && available; + return ( +

+ {ownPersonal && ( +
+
+ +
+

Personal default

+

{aiMethodLabel(account.provider, account.method)}

+
+
+ {account.isDefault ? ( + + {available ? : } + {available ? "Your default" : "Default unavailable"} + + ) : !readOnly ? ( + + ) : Not your default} +
+ )} +
+
+

{aiMethodLabel(account.provider, account.method)}

+ {account.accountLabel &&

{account.accountLabel}

} +
+ {!readOnly && grant.capabilities?.canRevoke && ( +
+ {} + {account.status !== "revoked" && } +
+ )} +
+ {revoking && setRevoking(false)} onConfirm={async () => { setRevokePending(true); setRevokeError(undefined); try { await onRevoke(); setRevoking(false); } catch (error) { setRevokeError(error instanceof Error ? error.message : "Could not revoke this account. Retry."); } finally { setRevokePending(false); } }}>{revokeError &&

{revokeError}

}{revocationDetails}
} +
+ ); +} diff --git a/ui/src/components/ai-connections/AiConnectionAuth.test.tsx b/ui/src/components/ai-connections/AiConnectionAuth.test.tsx new file mode 100644 index 0000000000..7657f588c1 --- /dev/null +++ b/ui/src/components/ai-connections/AiConnectionAuth.test.tsx @@ -0,0 +1,91 @@ +// @vitest-environment jsdom +import React from "react"; +import { createRoot, type Root } from "react-dom/client"; +import { flushSync } from "react-dom"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import { + AiConnectionAuth, + type AiConnectionAuthProps, +} from "./AiConnectionAuth"; + +let root: Root | undefined; +afterEach(() => { + if (root) flushSync(() => root?.unmount()); + root = undefined; + document.body.innerHTML = ""; +}); +function mount(overrides: Partial = {}) { + const props: AiConnectionAuthProps = { + provider: "openai", + method: "api_key", + state: { phase: "idle" }, + onStart: vi.fn(), + onSubmit: vi.fn(), + onCancel: vi.fn(), + onDone: vi.fn(), + ...overrides, + }; + const container = document.createElement("div"); + document.body.append(container); + root = createRoot(container); + const render = (next: Partial) => + flushSync(() => root!.render()); + render({}); + return { props, container, render }; +} +function typeInput(input: HTMLInputElement, value: string) { + flushSync(() => { + Object.getOwnPropertyDescriptor( + HTMLInputElement.prototype, + "value", + )!.set!.call(input, value); + input.dispatchEvent(new Event("input", { bubbles: true })); + }); +} + +describe("AI connection authentication presentation", () => { + it("masks keys, hands them only to the injected action, and clears after submission", () => { + const { container, props } = mount(); + const input = container.querySelector("input")!; + expect(input.type).toBe("password"); + typeInput(input, "example-only-key"); + flushSync(() => + input.dispatchEvent( + new KeyboardEvent("keydown", { key: "Enter", bubbles: true }), + ), + ); + expect(props.onSubmit).toHaveBeenCalledWith("example-only-key"); + expect(input.value).toBe(""); + expect(container.textContent).not.toContain("example-only-key"); + }); + it("drops private input when the provider or lifecycle phase changes", () => { + const { container, render } = mount(); + typeInput(container.querySelector("input")!, "example-only-key"); + render({ provider: "anthropic" }); + expect(container.querySelector("input")!.value).toBe(""); + typeInput(container.querySelector("input")!, "retry-key"); + render({ state: { phase: "error", message: "Rejected" } }); + expect(container.querySelector("input")!.value).toBe(""); + }); + it("cancellation clears input and invokes only cancellation", () => { + const { container, props } = mount(); + typeInput(container.querySelector("input")!, "example-only-key"); + flushSync(() => + [...container.querySelectorAll("button")] + .find((button) => button.textContent === "Cancel")! + .click(), + ); + expect(props.onCancel).toHaveBeenCalledOnce(); + expect(props.onSubmit).not.toHaveBeenCalled(); + expect(container.querySelector("input")!.value).toBe(""); + }); + it("never offers an OpenRouter subscription or starts a provider call on render", () => { + const { container, props } = mount({ + provider: "openrouter", + method: "subscription", + }); + expect(container.textContent).toContain("does not offer a subscription"); + expect(container.querySelector("input")).toBeNull(); + expect(props.onStart).not.toHaveBeenCalled(); + }); +}); diff --git a/ui/src/components/ai-connections/AiConnectionAuth.tsx b/ui/src/components/ai-connections/AiConnectionAuth.tsx new file mode 100644 index 0000000000..3e03b4e634 --- /dev/null +++ b/ui/src/components/ai-connections/AiConnectionAuth.tsx @@ -0,0 +1,190 @@ +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { + OnboardingCardField, + OnboardingLoginCodeRow, + ProviderApiKeyCard, + ProviderSubscriptionCard, +} from "@/components/AdapterLoginChrome"; +import { + AI_PROVIDERS, + aiMethodLabel, + type AiAuthMethod, + type AiProvider, +} from "./model"; + +/** Redacted view of the existing login lifecycle, supplied by the host. */ +export type AiAuthState = + | { phase: "idle" | "starting" | "submitting" | "connected" | "cancelled" } + | { phase: "waiting"; authorizationUrl: string; code?: string } + | { phase: "error" | "expired" | "unsupported"; message: string }; + +export interface AiConnectionAuthProps { + provider: AiProvider; + method: AiAuthMethod; + state: AiAuthState; + onStart: () => void; + onSubmit: (value: string) => void; + onCancel: () => void; + onDone: () => void; +} + +/** No provider calls or polling here: live hosts keep the existing login controllers. */ +export function AiConnectionAuth(props: AiConnectionAuthProps) { + // Remount private input state when the provider, method, or attempt changes phase. + return ( + + ); +} + +function AuthAttempt({ + provider, + method, + state, + onStart, + onSubmit, + onCancel, + onDone, +}: AiConnectionAuthProps) { + const [value, setValue] = useState(""); + const info = AI_PROVIDERS[provider]; + const busy = state.phase === "starting" || state.phase === "submitting"; + const unsupported = + state.phase === "unsupported" || + (method === "subscription" && !info.subscriptionName); + const submit = () => { + if (!value.trim() || busy) return; + const submitted = value.trim(); + setValue(""); + onSubmit(submitted); + }; + return ( +
+
+

Connect {info.name}

+

+ {aiMethodLabel(provider, method)} +

+
+ {state.phase === "connected" ? ( + <> +

+ Connected. This account is saved in Connections and can be reused. +

+ + + ) : ( + <> + {unsupported ? ( +

+ {state.phase === "unsupported" + ? state.message + : "This provider does not offer a subscription connection."} +

+ ) : ( + <> + {(state.phase === "error" || state.phase === "expired") && ( +

+ {state.message} +

+ )} + {state.phase === "cancelled" && ( +

+ Sign-in cancelled. No connection was created. +

+ )} + {method === "api_key" ? ( + + ) : busy ? ( + + + + ) : state.phase === "waiting" ? ( + + {provider === "anthropic" ? ( + + ) : ( + + )} + + ) : ( +

+ Sign in with your {info.subscriptionName}. +

+ )} + + )} +
+ + {!unsupported && + (method === "api_key" ? ( + + ) : state.phase === "waiting" ? ( + provider === "anthropic" ? ( + + ) : ( + + Waiting for sign-in… + + ) + ) : ( + + ))} +
+ + )} +
+ ); +} diff --git a/ui/src/components/ai-connections/AiConnectionCredentialStep.tsx b/ui/src/components/ai-connections/AiConnectionCredentialStep.tsx new file mode 100644 index 0000000000..ceaada992f --- /dev/null +++ b/ui/src/components/ai-connections/AiConnectionCredentialStep.tsx @@ -0,0 +1,113 @@ +import { useState } from "react"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { type AiProvider, type AiAuthMethod, type AiConnectionLoginIntent } from "@paperclipai/shared"; +import { AgentProviderConnection } from "@/components/new-agent/AgentProviderConnection"; +import { ProviderApiKeyCard } from "@/components/AdapterLoginChrome"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { aiConnectionsApi } from "@/api/ai-connections"; +import { environmentsApi } from "@/api/environments"; +import { instanceSettingsApi } from "@/api/instanceSettings"; +import { queryKeys } from "@/lib/queryKeys"; +import { resolveAdapterTestEnvironmentId, resolveLocalDefaultEnvironmentId, resolveManagedSandboxEnvironmentId } from "@/lib/adapter-test-environment"; +import { resolveForcedKubernetesEnvironment } from "@/lib/forced-kubernetes-environment"; + +type Props = { + companyId: string; + provider: AiProvider; + initialMethod?: AiAuthMethod; + fixedMethod?: boolean; + connectionId?: string; + name: string; + ownership: "personal" | "shared"; + agentIds: string[]; + allAgents: boolean; + environmentId?: string; + onComplete: (result: { connectionId: string; grantId: string; method: AiAuthMethod }) => void; + onCancel: () => void; +}; + +/** Connections hosts the same provider step as agent setup, with its own save intent. */ +export function AiConnectionCredentialStep(props: Props) { + if (props.provider === "openrouter") return ; + return ; +} + +function SubscriptionConnectionStep({ companyId, provider, initialMethod, fixedMethod, connectionId, name: initialName, ownership, agentIds, allAgents, environmentId: suppliedEnvironmentId, onComplete, onCancel }: Props) { + const [name, setName] = useState(initialName); + const [chosenEnvironment, setChosenEnvironment] = useState(); + const client = useQueryClient(); + const envs = useQuery({ queryKey: queryKeys.environments.list(companyId), queryFn: () => environmentsApi.list(companyId) }); + const caps = useQuery({ queryKey: queryKeys.environments.capabilities(companyId), queryFn: () => environmentsApi.capabilities(companyId) }); + const settings = useQuery({ queryKey: queryKeys.instance.settings, queryFn: instanceSettingsApi.get }); + const experimental = useQuery({ queryKey: queryKeys.instance.experimentalSettings, queryFn: instanceSettingsApi.getExperimental }); + const general = useQuery({ queryKey: queryKeys.instance.generalSettings, queryFn: instanceSettingsApi.getGeneral }); + const forced = resolveForcedKubernetesEnvironment(general.data?.executionMode, envs.data ?? []); + let environmentId: string | null = null; + let environmentError: string | undefined; + try { + environmentId = forced.forced ? forced.kubernetesEnvironment?.id ?? null : resolveAdapterTestEnvironmentId({ + agentDefaultEnvironmentId: suppliedEnvironmentId ?? chosenEnvironment, + instanceDefaultEnvironmentId: settings.data?.defaultEnvironmentId, + localDefaultEnvironmentId: resolveLocalDefaultEnvironmentId(envs.data), + managedSandboxOnly: experimental.data?.enableManagedSandboxOnly, + managedSandboxEnvironmentId: resolveManagedSandboxEnvironmentId(envs.data), + visibleEnvironmentIds: envs.data?.map((env) => env.id), + }); + } catch (error) { environmentError = error instanceof Error ? error.message : "Could not resolve the sign-in environment."; } + const loginEnvironments = (envs.data ?? []).filter((env) => + env.status === "active" && (env.driver === "local" || (env.driver === "sandbox" && + typeof env.config.provider === "string" && + caps.data?.sandboxProviders?.[env.config.provider]?.supportsLoginPty === true)), + ); + // Signing in may use a different environment from later agent execution. + // Prefer a supported login environment without changing any agent routing. + if (!forced.forced && !suppliedEnvironmentId && !chosenEnvironment && + !loginEnvironments.some((env) => env.id === environmentId)) { + environmentId = loginEnvironments[0]?.id ?? null; + } + const environment = envs.data?.find((env) => env.id === environmentId); + const sandboxProvider = typeof environment?.config.provider === "string" ? environment.config.provider : ""; + const canLogin = environment?.driver === "sandbox" && caps.data?.sandboxProviders?.[sandboxProvider]?.supportsLoginPty === true; + const loading = [envs, caps, settings, experimental, general].some((query) => query.isPending); + const error = environmentError ?? [envs, caps, settings, experimental, general].find((query) => query.error)?.error?.message; + const intent: AiConnectionLoginIntent = { provider, method: "subscription", name, ownership, agentIds, allAgents, connectionId }; + return
+ + {!suppliedEnvironmentId && !forced.forced && loginEnvironments.length > 1 && } + {error &&

{error}

} + {loading ?

Preparing sign-in…

: {}} + testConnection={async () => false} + managedAccount={{ intent, initialMethod, fixedMethod: fixedMethod || Boolean(connectionId), disabled: loading || Boolean(error) || !name.trim(), onComplete: (result) => { void client.invalidateQueries({ queryKey: ["ai-connections", companyId] }); onComplete(result); } }} + />} +
; +} + +function ApiKeyConnectionStep({ companyId, provider, connectionId, name: initialName, ownership, agentIds, allAgents, onComplete, onCancel }: Props) { + const [name, setName] = useState(initialName); + const [apiKey, setApiKey] = useState(""); + const client = useQueryClient(); + const save = useMutation({ + mutationFn: () => aiConnectionsApi.create(companyId, { provider, method: "api_key", name, ownership, agentIds, allAgents, connectionId, apiKey }), + onSuccess: (result) => { void client.invalidateQueries({ queryKey: ["ai-connections", companyId] }); onComplete({ ...result, method: "api_key" }); }, + onSettled: () => setApiKey(""), + }); + return
+ + {save.error &&

{save.error.message}

} + save.mutate()} disabled={save.isPending} placeholder="Enter API key here" autoFocus /> +
+
; +} diff --git a/ui/src/components/ai-connections/AiConnectionDesignExamples.tsx b/ui/src/components/ai-connections/AiConnectionDesignExamples.tsx new file mode 100644 index 0000000000..0e30c4bb31 --- /dev/null +++ b/ui/src/components/ai-connections/AiConnectionDesignExamples.tsx @@ -0,0 +1,68 @@ +import { useState } from "react"; +import { AiConnectionPicker } from "./AiConnectionPicker"; +import { LocalProviderLoginInstructions, ProviderApiKeyCard } from "@/components/AdapterLoginChrome"; +import type { + AiConnectionBinding, + AiConnectionRequirement, + AiConnectionSummary, +} from "./model"; + +const requirement: AiConnectionRequirement = { + companyId: "design-example", + provider: "anthropic", + method: "subscription", +}; +const account: AiConnectionSummary = { + ...requirement, + id: "example", + grantId: "example-grant", + name: "My Claude subscription", + ownership: "personal", + ownerUserId: "example-user", + ownerName: "You", + status: "connected", + isDefault: true, +}; + +export function AiConnectionDesignExamples() { + const [binding, setBinding] = useState({ + provider: "anthropic", + method: "subscription", + mode: "responsible_user", + }); + return ( +
+

+ Shared AI connection identity, account selection, and existing + authentication chrome. The full interactive state matrix lives in + Storybook under AI Connections / Review. Example controls below do not + connect accounts. +

+

Provider lists and account management use Browse and AppDetail from the Connectors interface. The picker below uses ConnectionChoiceList, also used by ConnectionSetupFlow.

+ {}} + /> + {}} + onSubmit={() => {}} + placeholder="Enter API key here" + /> + {} }} + /> +
+ ); +} diff --git a/ui/src/components/ai-connections/AiConnectionField.tsx b/ui/src/components/ai-connections/AiConnectionField.tsx new file mode 100644 index 0000000000..cf3a721a34 --- /dev/null +++ b/ui/src/components/ai-connections/AiConnectionField.tsx @@ -0,0 +1,180 @@ +import { useRef, useState } from "react"; +import { useQuery, useQueryClient } from "@tanstack/react-query"; +import { + aiConnectionBindingSchema, + isAiConnectionCompatible, + type AiConnectionBinding, + type AiAuthMethod, + type AiProvider, +} from "@paperclipai/shared"; +import { aiConnectionsApi } from "@/api/ai-connections"; +import { AiConnectionPicker } from "./AiConnectionPicker"; +import { AiConnectionLegacyNotice } from "./AiConnectionManagement"; +import { AiConnectionCredentialStep } from "./AiConnectionCredentialStep"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, + DialogFooter, +} from "@/components/ui/dialog"; + +export function aiProviderForAdapter( + adapterType: string, +): AiProvider | undefined { + return ( + { + claude_local: "anthropic", + codex_local: "openai", + opencode_local: "openrouter", + grok_local: "xai", + } as Record + )[adapterType]; +} +export function AiConnectionField({ + companyId, + agentId, + agentName, + adapterType, + model, + value, + onChange, + environmentId, + legacy = false, + readOnly = false, +}: { + companyId: string; + agentId?: string; + agentName: string; + adapterType: string; + model?: string; + value?: AiConnectionBinding; + onChange: (binding: AiConnectionBinding) => void; + environmentId?: string; + legacy?: boolean; + readOnly?: boolean; +}) { + const provider = aiProviderForAdapter(adapterType); + const returnFocus = useRef(null); + const restoreFocus = (event: Event) => { event.preventDefault(); returnFocus.current?.focus(); }; + const [adopting, setAdopting] = useState(false); + const [pendingAdoption, setPendingAdoption] = useState(); + const [connecting, setConnecting] = useState(false); + const method: AiAuthMethod = + value?.method ?? (provider === "openrouter" ? "api_key" : "subscription"); + const changeBinding = (next: AiConnectionBinding) => { + if (legacy && !value) { if (!connecting) returnFocus.current = document.activeElement as HTMLElement; setPendingAdoption(next); } + else onChange(next); + }; + const client = useQueryClient(); + const accounts = useQuery({ + queryKey: ["ai-connections", companyId, agentId], + queryFn: () => aiConnectionsApi.list(companyId, agentId), + enabled: Boolean(provider), + }); + if (!provider) return null; + if (legacy && !value && !adopting) + return ( + setAdopting(true)} + /> + ); + return ( +
+ {value && (adapterType !== "opencode_local" || Boolean(model)) && !isAiConnectionCompatible(value, adapterType, model) && ( +

+ This connection does not support the current harness and model. Choose + a compatible connection before saving. +

+ )} + + changeBinding(aiConnectionBindingSchema.parse(binding)) + } + onConnect={() => { returnFocus.current = document.activeElement as HTMLElement; setConnecting(true); }} + onRetry={() => void accounts.refetch()} + /> + { + if (!open) setPendingAdoption(undefined); + }} + > + + + Adopt Connections for {agentName} + + Saving tests this account in {agentName}’s environment before + replacing its existing authentication. Other agents keep their + current configuration. + + +

+ {pendingAdoption?.mode === "responsible_user" + ? `Responsible user’s default. For you: ${accounts.data?.connections.find((account) => account.isDefault && account.provider === provider && account.method === pendingAdoption.method)?.name ?? "Not connected"}. Other users use their own default.` + : accounts.data?.connections.find( + (account) => account.id === pendingAdoption?.connectionId, + )?.name} +

+

+ After adoption, missing credentials block execution. Previous + authentication will not be used as a fallback. +

+ + + + +
+
+ + + + Connect account + + setConnecting(false)} + onComplete={({ method: connectedMethod }) => { + void client.invalidateQueries({ + queryKey: ["ai-connections", companyId], + }); + setConnecting(false); + changeBinding({ provider, method: connectedMethod, mode: "responsible_user" }); + }} + /> + + +
+ ); +} diff --git a/ui/src/components/ai-connections/AiConnectionIdentity.tsx b/ui/src/components/ai-connections/AiConnectionIdentity.tsx new file mode 100644 index 0000000000..b35aebbbfd --- /dev/null +++ b/ui/src/components/ai-connections/AiConnectionIdentity.tsx @@ -0,0 +1,37 @@ +import { Building2, UserRound } from "lucide-react"; +import { AppLogo } from "@/pages/apps/AppLogo"; +import { + AI_PROVIDERS, + aiMethodLabel, + type AiConnectionSummary, +} from "./model"; + +export function AiConnectionIdentity({ + connection, +}: { + connection: AiConnectionSummary; +}) { + const provider = AI_PROVIDERS[connection.provider]; + const Icon = connection.ownership === "shared" ? Building2 : UserRound; + return ( +
+ +
+ + {connection.name} + + + {provider.name} ·{" "} + {aiMethodLabel(connection.provider, connection.method)} + {connection.accountLabel ? ` · ${connection.accountLabel}` : ""} + + + + {connection.ownership === "shared" + ? "Company shared" + : `Personal · ${connection.ownerName ?? "Account owner"}`} + +
+
+ ); +} diff --git a/ui/src/components/ai-connections/AiConnectionManagement.tsx b/ui/src/components/ai-connections/AiConnectionManagement.tsx new file mode 100644 index 0000000000..f45ab1c680 --- /dev/null +++ b/ui/src/components/ai-connections/AiConnectionManagement.tsx @@ -0,0 +1,27 @@ +import { Button } from "@/components/ui/button"; + +export function AiConnectionLegacyNotice({ + onAdopt, + readOnly = false, +}: { + onAdopt: () => void; + readOnly?: boolean; +}) { + return ( +
+

+ Existing authentication — not managed by Connections +

+

+ This agent keeps its current authentication until you choose and test a + managed connection. Confirm the account and who may use it before + adopting. +

+ {!readOnly && ( + + )} +
+ ); +} diff --git a/ui/src/components/ai-connections/AiConnectionPicker.tsx b/ui/src/components/ai-connections/AiConnectionPicker.tsx new file mode 100644 index 0000000000..f45d813225 --- /dev/null +++ b/ui/src/components/ai-connections/AiConnectionPicker.tsx @@ -0,0 +1,144 @@ +import { AppLogo } from "@/pages/apps/AppLogo"; +import { ConnectionChoiceList } from "@/features/connections/ConnectionChoiceList"; +import { Button } from "@/components/ui/button"; +import { Skeleton } from "@/components/ui/skeleton"; +import { + AI_PROVIDERS, + aiConnectionProblem, + aiMethodLabel, + bindingProblem, + matchesAiRequirement, + personalAiDefault, + type AiConnectionBinding, + type AiConnectionRequirement, + type AiConnectionSummary, +} from "./model"; + +export interface AiConnectionPickerProps { + requirement: AiConnectionRequirement; + connections: AiConnectionSummary[]; + value?: AiConnectionBinding; + currentUserId: string; + agentId: string; + agentName: string; + loading?: boolean; + error?: string; + readOnly?: boolean; + onChange: (binding: AiConnectionBinding) => void; + onConnect: () => void; + onRetry?: () => void; +} + +export function AiConnectionPicker({ + requirement, + connections, + value, + currentUserId, + agentId, + loading, + error, + readOnly, + onChange, + onConnect, + onRetry, +}: AiConnectionPickerProps) { + const compatible = connections.filter((connection) => + matchesAiRequirement(connection, requirement), + ); + const personalDefault = personalAiDefault( + compatible, + requirement, + currentUserId, + ); + const problem = value ? bindingProblem( + value, + requirement, + compatible, + currentUserId, + agentId, + ) : undefined; + const select = ( + mode: "shared", + connection: AiConnectionSummary, + ) => + onChange({ + provider: requirement.provider, + method: requirement.method, + mode, + connectionId: connection.id, + grantId: connection.grantId, + }); + return ( +
+
+ +
+

AI connection

+

+ {AI_PROVIDERS[requirement.provider].name} ·{" "} + {aiMethodLabel(requirement.provider, requirement.method)} +

+
+
+ {loading ? ( +
+ +
+ ) : error ? ( +
+

+ {error} +

+ {onRetry && ( + + )} +
+ ) : ( + <> + + For you: {personalDefault?.name ?? "Not connected"} + Other users’ tasks use their own {requirement.method === "api_key" ? `${AI_PROVIDERS[requirement.provider].name} API key` : aiMethodLabel(requirement.provider, requirement.method)}. + }, + ...compatible.filter((connection) => connection.ownership === "shared").map((connection) => ({ + id: connection.id, name: connection.name, + disabled: Boolean(aiConnectionProblem(connection)), + description: <>Company shared{connection.accountLabel ? ` · ${connection.accountLabel}` : ""}{aiConnectionProblem(connection) ? ` · ${aiConnectionProblem(connection)}` : ""}, + })), + ]} + onSelect={(id) => { + if (id === "responsible_user") onChange({provider: requirement.provider, method: requirement.method, mode: "responsible_user"}); + else { const connection = compatible.find((item) => item.id === id)!; select("shared", connection); } + }} + /> + {problem && ( +

+ {problem} +

+ )} + {!readOnly && ( + + )} + + )} +
+ ); +} diff --git a/ui/src/components/ai-connections/ManagedAiConnectionDetails.tsx b/ui/src/components/ai-connections/ManagedAiConnectionDetails.tsx new file mode 100644 index 0000000000..ede259e7b7 --- /dev/null +++ b/ui/src/components/ai-connections/ManagedAiConnectionDetails.tsx @@ -0,0 +1,134 @@ +import { heartbeatsApi } from "@/api/heartbeats"; +import { Button } from "@/components/ui/button"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { aiConnectionsApi } from "@/api/ai-connections"; +import { toolsApi } from "@/api/tools"; +import { useNavigate } from "@/lib/router"; +import { AiConnectionAccountControls } from "./AiConnectionAccountControls"; +import type { ToolConnection } from "@paperclipai/shared"; +import { aiMethodLabel } from "./model"; + +export function ManagedAiConnectionRow({ + connection, +}: { + connection: ToolConnection; +}) { + const metadata = connection.config?.ai as + | { + provider: "anthropic" | "openai" | "openrouter" | "xai"; + method: "subscription" | "api_key"; + } + | undefined; + if (!metadata) return null; + return ( +

+ {aiMethodLabel(metadata.provider, metadata.method)} ·{" "} + {connection.credentialPolicy === "per_user" + ? "Personal" + : "Company shared"} +

+ ); +} +export function ManagedAiConnectionDetails({ + connection, +}: { + connection: ToolConnection; +}) { + const client = useQueryClient(); + const navigate = useNavigate(); + const runs = useQuery({ + queryKey: ["ai-connection-active-runs", connection.id], + queryFn: () => + aiConnectionsApi.activeRuns(connection.companyId, connection.id), + }); + const accounts = useQuery({ + queryKey: ["ai-connections", connection.companyId], + queryFn: () => aiConnectionsApi.list(connection.companyId), + }); + const grants = useQuery({ + queryKey: ["ai-connection-grants", connection.id], + queryFn: () => toolsApi.listConnectionGrants(connection.id), + }); + const refresh = () => client.invalidateQueries(); + const makeDefault = useMutation({ + mutationFn: (id: string) => + aiConnectionsApi.setDefault(connection.companyId, id), + onSuccess: refresh, + }); + const revoke = useMutation({ + mutationFn: (id: string) => + toolsApi.revokeConnectionGrant(connection.id, id), + onSuccess: refresh, + }); + const stop = useMutation({ + mutationFn: (id: string) => heartbeatsApi.cancel(id), + onSuccess: refresh, + }); + const account = accounts.data?.connections.find( + (a) => a.id === connection.id, + ); + const grant = grants.data?.grants.find((g) => g.id === account?.grantId); + const error = + accounts.error ?? + grants.error ?? + makeDefault.error ?? + stop.error; + if (error) + return ( +

+ {error.message} +

+ ); + if (!account || !grant) + return ( +

+ {accounts.isPending || grants.isPending + ? "Loading AI account…" + : "This account is not available to you."} +

+ ); + return ( +
+ makeDefault.mutate(grant.id)} + onRevoke={() => revoke.mutateAsync(grant.id).then(() => undefined)} + revocationDetails={ +
+ {runs.error && ( +

+ Could not load active runs. Retry before revoking. +

+ )} + {runs.data?.map((run) => ( +
+ + {run.agentName} · {run.status} + + +
+ ))} +
+ } + onReconnect={() => + navigate( + `/apps/connect?source=${account.provider}&reconnect=${connection.id}&method=ai-${account.method}`, + ) + } + /> + +
+ ); +} diff --git a/ui/src/components/ai-connections/model.test.ts b/ui/src/components/ai-connections/model.test.ts new file mode 100644 index 0000000000..0141f0aeff --- /dev/null +++ b/ui/src/components/ai-connections/model.test.ts @@ -0,0 +1,148 @@ +import { describe, expect, it } from "vitest"; +import { + aiConnectionProblem, + bindingProblem, + matchesAiRequirement, + personalAiDefault, + type AiConnectionSummary, + type AiConnectionRequirement, + type AiConnectionBinding, +} from "./model"; + +const requirement: AiConnectionRequirement = { + companyId: "company", + provider: "anthropic", + method: "subscription", +}; +const account: AiConnectionSummary = { + ...requirement, + id: "connection", + grantId: "grant", + name: "Personal Claude", + ownership: "personal", + ownerUserId: "alice", + status: "connected", + isDefault: true, +}; +const binding: AiConnectionBinding = { + provider: "anthropic", + method: "subscription", + mode: "responsible_user", +}; + +describe("AI connection selection presentation", () => { + it("scopes personal defaults to company, user, provider and method", () => { + for (const change of [ + { companyId: "other" }, + { provider: "openai" as const }, + { method: "api_key" as const }, + { ownerUserId: "bob" }, + { ownership: "shared" as const }, + ]) { + expect( + personalAiDefault([{ ...account, ...change }], requirement, "alice"), + ).toBeUndefined(); + } + expect(personalAiDefault([account], requirement, "alice")).toBe(account); + }); + it("retains a revoked default instead of falling back to a healthy account", () => { + const revoked = { ...account, status: "revoked" as const }; + const alternate = { ...account, id: "alternate", isDefault: false }; + expect(personalAiDefault([alternate, revoked], requirement, "alice")).toBe( + revoked, + ); + expect( + bindingProblem( + binding, + requirement, + [alternate, revoked], + "alice", + "agent", + ), + ).toContain("Revoked"); + }); + it("does not select another user’s account", () => { + expect( + bindingProblem(binding, requirement, [account], "bob", "agent"), + ).toContain("No connection"); + }); + it("does not infer a default from the first compatible connection", () => { + expect( + personalAiDefault( + [{ ...account, isDefault: false }], + requirement, + "alice", + ), + ).toBeUndefined(); + }); + it("rejects incompatible bindings without modifying the requirement", () => { + const original = { ...requirement }; + expect( + bindingProblem( + { ...binding, provider: "openai" }, + requirement, + [account], + "alice", + "agent", + ), + ).toContain("compatible"); + expect(requirement).toEqual(original); + expect( + matchesAiRequirement({ ...account, method: "api_key" }, requirement), + ).toBe(false); + }); + it("requires exact grant identity and human access for a legacy personal selection", () => { + const delegated = { + provider: "anthropic", + method: "subscription", + mode: "delegated", + connectionId: account.id, + grantId: account.grantId, + } as const; + expect( + bindingProblem(delegated, requirement, [account], "bob", "agent"), + ).toContain("not shared with you"); + expect( + bindingProblem( + delegated, + requirement, + [account], + "alice", + "agent", + ), + ).toBeNull(); + expect( + bindingProblem( + { ...delegated, grantId: "different" }, + requirement, + [account], + "alice", + "agent", + ), + ).toContain("no longer available"); + }); + it("does not mistake a personal account for shared", () => { + expect( + bindingProblem( + { + ...binding, + mode: "shared", + connectionId: account.id, + grantId: account.grantId, + }, + requirement, + [account], + "alice", + "agent", + ), + ).toContain("company-shared"); + }); + it("preserves server-projected eligibility denials", () => { + expect( + aiConnectionProblem({ + ...account, + unavailableReason: "Not in the shared audience", + }), + ).toBe("Not in the shared audience"); + }); +}); diff --git a/ui/src/components/ai-connections/model.ts b/ui/src/components/ai-connections/model.ts new file mode 100644 index 0000000000..e017cc3f71 --- /dev/null +++ b/ui/src/components/ai-connections/model.ts @@ -0,0 +1,119 @@ +/** Redacted presentation contracts shared with the production API. */ +import type { AiProvider, AiAuthMethod, AiManagedConnectionSummary, AiConnectionBinding } from "@paperclipai/shared"; +export type { AiProvider, AiAuthMethod, AiConnectionBinding } from "@paperclipai/shared"; +export type AiConnectionStatus = AiManagedConnectionSummary["status"]; + +export const AI_PROVIDERS: Record< + AiProvider, + { name: string; subscriptionName?: string; logo?: string } +> = { + anthropic: { + name: "Claude", + subscriptionName: "Claude subscription", + logo: "/brands/claude-color.svg", + }, + openai: { + name: "OpenAI", + subscriptionName: "ChatGPT subscription", + logo: "/brands/codex-color.svg", + }, + openrouter: { name: "OpenRouter", logo: "/brands/apps/openrouter.svg" }, + xai: { + name: "Grok", + subscriptionName: "Grok subscription", + logo: "/brands/adapters/grok.svg", + }, +}; + +export type AiConnectionSummary = Omit & { isDefault?: boolean }; + +export interface AiConnectionRequirement { + companyId: string; + provider: AiProvider; + method: AiAuthMethod; +} + +export const AI_CONNECTION_STATUS: Record = { + connected: "Connected", + needs_attention: "Needs attention", + expired: "Expired", + revoked: "Revoked", +}; + +export function aiMethodLabel(provider: AiProvider, method: AiAuthMethod) { + return method === "subscription" + ? (AI_PROVIDERS[provider].subscriptionName ?? "Subscription unavailable") + : "API key"; +} + +export function matchesAiRequirement( + connection: AiConnectionSummary, + requirement: AiConnectionRequirement, +) { + return ( + connection.companyId === requirement.companyId && + connection.provider === requirement.provider && + connection.method === requirement.method + ); +} + +export function personalAiDefault( + connections: AiConnectionSummary[], + requirement: AiConnectionRequirement, + userId: string, +) { + // Never choose another account because the declared default is unhealthy. + return connections.find( + (connection) => + matchesAiRequirement(connection, requirement) && + connection.ownership === "personal" && + connection.ownerUserId === userId && + connection.isDefault, + ); +} + +export function aiConnectionProblem(connection?: AiConnectionSummary) { + if (!connection) + return "No connection selected. Connect an account to continue."; + return ( + connection.unavailableReason ?? + (connection.status === "connected" + ? null + : `${AI_CONNECTION_STATUS[connection.status]}. Reconnect this account to continue.`) + ); +} + +export function bindingProblem( + binding: AiConnectionBinding, + requirement: AiConnectionRequirement, + connections: AiConnectionSummary[], + userId: string, + _agentId: string, +) { + if ( + binding.provider !== requirement.provider || + binding.method !== requirement.method + ) + return "Choose a connection compatible with this provider and sign-in method."; + if (binding.mode === "responsible_user") + return aiConnectionProblem( + personalAiDefault(connections, requirement, userId), + ); + const connection = connections.find( + (item) => + item.id === binding.connectionId && + item.grantId === binding.grantId && + matchesAiRequirement(item, requirement), + ); + if (!connection) + return "This connection is no longer available for this agent. Choose another connection."; + if (binding.mode === "shared" && connection.ownership !== "shared") + return "Choose a company-shared connection."; + if ( + binding.mode === "delegated" && + (connection.ownership !== "personal" || + connection.ownerUserId !== userId) + ) + return "This credential is not shared with you. Choose a connection you can use."; + return aiConnectionProblem(connection); +} diff --git a/ui/src/components/ai-connections/useLocalAiLogin.test.tsx b/ui/src/components/ai-connections/useLocalAiLogin.test.tsx new file mode 100644 index 0000000000..4434632609 --- /dev/null +++ b/ui/src/components/ai-connections/useLocalAiLogin.test.tsx @@ -0,0 +1,81 @@ +// @vitest-environment jsdom +import { StrictMode } from "react"; +import { flushSync } from "react-dom"; +import { createRoot } from "react-dom/client"; +import { afterEach, beforeEach, expect, it, vi } from "vitest"; +import { useLocalAiLogin } from "./useLocalAiLogin"; +import { LocalProviderLoginInstructions } from "../AdapterLoginChrome"; + +const api = vi.hoisted(() => ({ startLocalLogin: vi.fn(), checkLocalLogin: vi.fn(), cancelLocalLogin: vi.fn(), connectLocal: vi.fn() })); +vi.mock("@/api/ai-connections", () => ({ aiConnectionsApi: api })); +let root: ReturnType; +let host: HTMLDivElement; +beforeEach(() => { + vi.resetAllMocks(); + api.startLocalLogin.mockImplementation(async () => ({ sessionId: "attempt-1", command: "isolated codex login", expiresAt: "2099-01-01T00:00:00Z" })); + api.checkLocalLogin.mockResolvedValue({ status: "sign_in_required" }); + api.cancelLocalLogin.mockResolvedValue({}); + api.connectLocal.mockResolvedValue({ connectionId: "connection", grantId: "grant" }); + host = document.createElement("div"); document.body.append(host); root = createRoot(host); +}); +afterEach(() => { flushSync(() => root.unmount()); host.remove(); }); +function Harness({ name = "Account", provider = "openai", enabled = true }: { name?: string; provider?: "anthropic" | "openai"; enabled?: boolean }) { + const login = useLocalAiLogin("company", { provider, method: "subscription", name, ownership: "personal", agentIds: [], allAgents: true }, enabled, { allowHostClaude: true }); + return <>; +} +it("checks once under StrictMode, preserves renaming and navigation, and cancels only on explicit retry", async () => { + flushSync(() => root.render()); + await vi.waitFor(() => expect(host.textContent).toContain("isolated codex login")); + expect(api.startLocalLogin).toHaveBeenCalledTimes(1); + expect(api.checkLocalLogin).toHaveBeenCalledTimes(1); + expect(api.cancelLocalLogin).not.toHaveBeenCalled(); + flushSync(() => root.render()); + expect(api.startLocalLogin).toHaveBeenCalledTimes(1); + flushSync(() => root.render()); + flushSync(() => root.render()); + await vi.waitFor(() => expect(host.textContent).toContain("isolated codex login")); + expect(api.cancelLocalLogin).not.toHaveBeenCalled(); + flushSync(() => Array.from(host.querySelectorAll('button')).find(b => b.textContent === 'Connect')!.click()); + await vi.waitFor(() => expect(api.connectLocal).toHaveBeenCalledWith("company", expect.objectContaining({ name: "Renamed", localSessionId: "attempt-1" }))); + flushSync(() => Array.from(host.querySelectorAll('button')).find(b => b.textContent === 'Start sign-in again')!.click()); + await vi.waitFor(() => expect(api.startLocalLogin).toHaveBeenCalledTimes(2)); + expect(api.cancelLocalLogin).toHaveBeenCalledTimes(1); + expect(api.cancelLocalLogin.mock.invocationCallOrder[0]).toBeLessThan(api.startLocalLogin.mock.invocationCallOrder[1]); +}); +it.each(["anthropic", "openai"] as const)("detects an already-signed-in %s account before showing instructions, and does not save it until Connect", async provider => { + api.checkLocalLogin.mockResolvedValue({ status: "ready" }); + flushSync(() => root.render()); + expect(host.textContent).toContain("Checking local"); + await vi.waitFor(() => expect(host.textContent).toContain("is signed in")); + expect(host.textContent).not.toContain("Run this in a terminal"); + expect(api.connectLocal).not.toHaveBeenCalled(); + if (provider === "anthropic") expect(api.startLocalLogin).not.toHaveBeenCalled(); +}); +it("detects terminal completion on focus without needing a Connect attempt", async () => { + flushSync(() => root.render()); + await vi.waitFor(() => expect(host.textContent).toContain("isolated codex login")); + api.checkLocalLogin.mockResolvedValue({ status: "ready" }); + window.dispatchEvent(new Event('focus')); + await vi.waitFor(() => expect(host.textContent).toContain("is signed in")); + expect(host.textContent).not.toContain("isolated codex login"); + expect(api.connectLocal).not.toHaveBeenCalled(); +}); +it("keeps a copied command's attempt alive after leaving the page", async () => { + flushSync(() => root.render()); + await vi.waitFor(() => expect(host.textContent).toContain("isolated codex login")); + flushSync(() => root.render(
Another page
)); + await new Promise(resolve => setTimeout(resolve, 10)); + expect(api.cancelLocalLogin).not.toHaveBeenCalled(); + const checks = api.checkLocalLogin.mock.calls.length; + window.dispatchEvent(new Event('focus')); + expect(api.checkLocalLogin).toHaveBeenCalledTimes(checks); +}); + +it("explicit retry can replace an attempt opened in another authentication host", async () => { + api.startLocalLogin.mockRejectedValueOnce(new Error("Another sign-in is still open.")); + flushSync(() => root.render()); + await vi.waitFor(() => expect(host.textContent).toContain("Another sign-in")); + flushSync(() => Array.from(host.querySelectorAll('button')).find(b => b.textContent === 'Start sign-in again')!.click()); + await vi.waitFor(() => expect(host.textContent).toContain("isolated codex login")); + expect(api.startLocalLogin).toHaveBeenLastCalledWith("company", expect.objectContaining({ restart: true })); +}); diff --git a/ui/src/components/ai-connections/useLocalAiLogin.ts b/ui/src/components/ai-connections/useLocalAiLogin.ts new file mode 100644 index 0000000000..4d84cf7297 --- /dev/null +++ b/ui/src/components/ai-connections/useLocalAiLogin.ts @@ -0,0 +1,92 @@ +import { useEffect, useRef, useState } from "react"; +import type { AiConnectionLoginIntent, LocalAiLoginAttempt, LocalAiLoginStatus } from "@paperclipai/shared"; +import { aiConnectionsApi } from "@/api/ai-connections"; + +/** Every authentication host uses the same local credential check and login lifecycle. */ +export function useLocalAiLogin(companyId: string | null, intent: AiConnectionLoginIntent, enabled: boolean, options: { allowHostClaude?: boolean } = {}) { + const isolated = intent.provider !== "anthropic" || !options.allowHostClaude; + const active = Boolean(companyId && enabled); + const [attempt, setAttempt] = useState(null); + const [status, setStatus] = useState(null); + const [error, setError] = useState(null); + const [generation, setGeneration] = useState(0); + const latestIntent = useRef(intent); + const restartRequested = useRef(false); + const pending = useRef>(Promise.resolve()); + const current = useRef<{ key: string; companyId: string; request: Promise } | null>(null); + function cancelCurrent() { + const previous = current.current; + current.current = null; + if (previous) pending.current = previous.request + .then((result) => aiConnectionsApi.cancelLocalLogin(previous.companyId, result.sessionId)).catch(() => {}); + } + latestIntent.current = intent; + // Renaming the account does not restart sign-in; access/target changes do. + const target = JSON.stringify({ ...intent, name: undefined }); + useEffect(() => { + setAttempt(null); + setError(null); + setStatus(null); + if (!active || !companyId) return; + let cancelled = false; + let checking = false; + let timer: ReturnType | undefined; + const key = JSON.stringify([companyId, target, generation]); + if (isolated && current.current?.key !== key) { + cancelCurrent(); + const input = { ...latestIntent.current, ...(restartRequested.current ? { restart: true } : {}) }; + restartRequested.current = false; + const request = pending.current.then(() => aiConnectionsApi.startLocalLogin(companyId, input)); + current.current = { key, companyId, request }; + pending.current = request.catch(() => {}); + } + const request = isolated ? current.current!.request : Promise.resolve(null); + async function check() { + if (checking || cancelled) return; + checking = true; + clearTimeout(timer); + try { + const result = await request; + if (cancelled) return; + setAttempt(result); + const next = await aiConnectionsApi.checkLocalLogin(companyId!, { + ...latestIntent.current, ...(result ? { localSessionId: result.sessionId } : {}), + }); + if (cancelled) return; + setStatus(next.status); + setError(next.status === "expired" ? "This sign-in attempt expired. Start sign-in again." : null); + // Stop polling a verified account. Focus still rechecks after a terminal + // visit; awaiting terminal login never requires repeated Connect clicks. + if (next.status === "sign_in_required") timer = setTimeout(() => void check(), 5000); + } catch (cause) { + if (!cancelled) setError(cause instanceof Error ? cause.message : "Could not check local sign-in."); + } finally { checking = false; } + } + const onFocus = () => { if (!document.hidden) void check(); }; + void check(); + window.addEventListener("focus", onFocus); + document.addEventListener("visibilitychange", onFocus); + return () => { + cancelled = true; + clearTimeout(timer); + window.removeEventListener("focus", onFocus); + document.removeEventListener("visibilitychange", onFocus); + // Navigation is not cancellation. The server resumes this bounded attempt + // when the user returns and reaps abandoned attempts after expiry. Deleting + // here made copied CODEX_HOME commands point at nonexistent directories. + }; + }, [companyId, active, isolated, target, generation]); + return { + isolated, + command: attempt?.command, + status, + preparing: active && !status && !error, + error, + retry: () => { restartRequested.current = true; cancelCurrent(); setGeneration((value) => value + 1); }, + connect: (input = intent) => { + if (!companyId) throw new Error("Choose a company before connecting."); + if (isolated && !attempt) throw new Error("Prepare local sign-in before connecting."); + return aiConnectionsApi.connectLocal(companyId, { ...input, ...(attempt ? { localSessionId: attempt.sessionId } : {}) }); + }, + }; +} diff --git a/ui/src/components/chat/AgentChannelsPanel.tsx b/ui/src/components/chat/AgentChannelsPanel.tsx index 24f1e0f113..d444ff5491 100644 --- a/ui/src/components/chat/AgentChannelsPanel.tsx +++ b/ui/src/components/chat/AgentChannelsPanel.tsx @@ -13,6 +13,7 @@ const providerNames: Record = { discord: "Discord", "microsoft-teams": "Microsoft Teams", telegram: "Telegram", + "imessage-photon": "iMessage Photon", agentmail: "AgentMail", }; diff --git a/ui/src/components/chat/ExternallyConnectedTaskBanner.tsx b/ui/src/components/chat/ExternallyConnectedTaskBanner.tsx index c9558ff4b1..9a5eee6b8d 100644 --- a/ui/src/components/chat/ExternallyConnectedTaskBanner.tsx +++ b/ui/src/components/chat/ExternallyConnectedTaskBanner.tsx @@ -38,6 +38,7 @@ const providerNames: Record = { "microsoft-teams": "Microsoft Teams", telegram: "Telegram", agentmail: "AgentMail", + "imessage-photon": "iMessage Photon", }; type PublicationFeedback = { diff --git a/ui/src/components/new-agent/AgentProviderConnection.test.tsx b/ui/src/components/new-agent/AgentProviderConnection.test.tsx index 2116e0e6cc..c4301be34f 100644 --- a/ui/src/components/new-agent/AgentProviderConnection.test.tsx +++ b/ui/src/components/new-agent/AgentProviderConnection.test.tsx @@ -10,7 +10,18 @@ const mocks = vi.hoisted(() => ({ login: vi.fn(), personal: vi.fn(), organization: vi.fn(), + loginPanel: vi.fn(), })); +const managedApi = vi.hoisted(() => ({ + list: vi.fn(async () => ({ currentUserId: "user-1", connections: [] })), + loginResult: vi.fn(async () => ({ connectionId: "login-account", grantId: "login-grant" })), + connectLocal: vi.fn(async () => ({ connectionId: "local-account", grantId: "local-grant" })), + startLocalLogin: vi.fn(async () => ({ sessionId: "local-attempt", command: "CODEX_HOME='/fixture/isolated-login' codex login", expiresAt: "2026-09-11T20:00:00Z" })), + checkLocalLogin: vi.fn(async () => ({ status: "sign_in_required" as const })), + cancelLocalLogin: vi.fn(async () => ({})), + create: vi.fn(async () => ({ connectionId: "managed-connection", grantId: "managed-grant" })), +})); +vi.mock("@/api/ai-connections", () => ({ aiConnectionsApi: managedApi })); vi.mock("@/api/agents", () => ({ agentsApi: { getAdapterAuthSignal: mocks.auth, @@ -21,7 +32,7 @@ vi.mock("@/api/secrets", () => ({ secretsApi: { listMyUserSecrets: mocks.personal, list: mocks.organization }, })); vi.mock("../AgentConfigForm", () => ({ - AdapterLoginPanel: () =>
New subscription login
, + AdapterLoginPanel: (props: unknown) => { mocks.loginPanel(props); return
New subscription login
; }, })); let root: Root; let host: HTMLDivElement; @@ -39,6 +50,10 @@ async function mount( codexSubscriptions = false, savedApiKeys = true, cachedClaudeLogin = false, + managedAccount?: Parameters[0]["managedAccount"], + localEnvironment = false, + deploymentMode: "local_trusted" | "authenticated" = "local_trusted", + localAiLoginSupported = true, ) { const key = adapterType === "claude_local" ? "ANTHROPIC_API_KEY" : "OPENAI_API_KEY"; @@ -91,6 +106,8 @@ async function mount( client.setQueryData(["claude-oauth-token-status", "c1"], { secretId: "cached-claude", latestVersion: 1 }); mocks.auth.mockResolvedValue({ status: "absent" }); } + client.setQueryData(["health"], { deploymentMode, localAiLoginSupported }); + client.setQueryDefaults(["health"], { staleTime: Infinity }); host = document.createElement("div"); document.body.appendChild(host); root = createRoot(host); @@ -104,16 +121,18 @@ async function mount( adapterType={adapterType} environmentId="e1" canLogin={canLogin} + localEnvironment={localEnvironment} onBack={() => {}} testConnection={test} onConnected={connected} + managedAccount={managedAccount} /> , ), ); await vi.waitFor(() => expect(mocks.personal).toHaveBeenCalled()); await vi.waitFor(() => expect(client.isFetching()).toBe(0)); - if (savedApiKeys) await vi.waitFor(() => expect(host.textContent).toContain("2 saved API keys")); + if (savedApiKeys && !managedAccount) await vi.waitFor(() => expect(host.textContent).toContain("2 saved API keys")); return { test, connected, key }; } function click(text: string) { @@ -129,6 +148,124 @@ function openProvider() { ); } describe("AgentProviderConnection reuse", () => { + it.each(["claude_local", "codex_local"] as const)("does not offer a server-host command when health disables local login: %s", async adapterType => { + const onComplete = vi.fn(); + const intent = { provider: adapterType === "claude_local" ? "anthropic" as const : "openai" as const, method: "subscription" as const, name: "Hosted account", ownership: "personal" as const, agentIds: [], allAgents: false }; + await mount(adapterType, false, false, false, false, false, { intent, onComplete }, true, "authenticated", false); + openProvider(); + expect(host.textContent).toContain("This environment does not support browser sign-in"); + expect(host.textContent).not.toContain("Run this in a terminal"); + expect(managedApi.startLocalLogin).not.toHaveBeenCalled(); + click("Connect"); + expect(managedApi.connectLocal).not.toHaveBeenCalled(); + expect(onComplete).not.toHaveBeenCalled(); + }); + it.each(["claude_local", "codex_local"] as const)("prepares and completes an isolated subscription on an authenticated self-hosted instance: %s", async adapterType => { + const onComplete = vi.fn(); + const command = adapterType === "claude_local" ? "CLAUDE_CONFIG_DIR='/isolated/claude' claude auth login" : "CODEX_HOME='/isolated/codex' codex login --device-auth"; + managedApi.startLocalLogin.mockResolvedValue({ sessionId: "local-attempt", command, expiresAt: "2099-01-01T00:00:00Z" }); + const intent = { provider: adapterType === "claude_local" ? "anthropic" as const : "openai" as const, method: "subscription" as const, name: "Self-hosted account", ownership: "personal" as const, agentIds: [], allAgents: false }; + await mount(adapterType, false, false, false, false, false, { intent, onComplete }, true, "authenticated"); + openProvider(); + await vi.waitFor(() => expect(host.textContent).toContain(command)); + expect(host.textContent).toContain("Your existing terminal login stays separate"); + expect(host.textContent).not.toContain("Connect uses your local"); + expect(managedApi.startLocalLogin).toHaveBeenCalledWith("c1", intent); + expect(managedApi.checkLocalLogin).toHaveBeenCalledWith("c1", { ...intent, localSessionId: "local-attempt" }); + click("Connect"); + await vi.waitFor(() => expect(onComplete).toHaveBeenCalled()); + expect(managedApi.connectLocal).toHaveBeenCalledWith("c1", { ...intent, localSessionId: "local-attempt" }); + }); + it.each(["claude_local", "codex_local"] as const)("connects a local subscription without a sandbox and supports retry: %s", async (adapterType) => { + const onComplete = vi.fn(); + const intent = { provider: adapterType === "claude_local" ? "anthropic" as const : "openai" as const, method: "subscription" as const, name: "My account", ownership: "personal" as const, agentIds: [], allAgents: false }; + await mount(adapterType, false, false, false, false, false, { intent, onComplete }, true); + openProvider(); + await vi.waitFor(() => expect(host.textContent).toContain(adapterType === "claude_local" ? "claude auth login" : "codex login")); + expect(host.textContent).toContain("machine running Paperclip"); + expect(host.textContent).not.toContain("sandbox"); + managedApi.connectLocal.mockRejectedValueOnce(new Error("Run local login and try again")); + click("Connect"); + await vi.waitFor(() => expect(host.textContent).toContain("Run local login and try again")); + expect(onComplete).not.toHaveBeenCalled(); + if (adapterType === "codex_local") { + click("Start sign-in again"); + await vi.waitFor(() => expect(host.textContent).not.toContain("Run local login and try again")); + await vi.waitFor(() => expect(managedApi.cancelLocalLogin).toHaveBeenCalledWith("c1", "local-attempt")); + await vi.waitFor(() => expect(host.textContent).toContain("codex login")); + } + click("Connect"); + await vi.waitFor(() => expect(onComplete).toHaveBeenCalledWith({ connectionId: "local-account", grantId: "local-grant", method: "subscription" })); + expect(managedApi.connectLocal).toHaveBeenCalledWith("c1", adapterType === "codex_local" ? { ...intent, localSessionId: "local-attempt" } : intent); + expect(mocks.loginPanel).not.toHaveBeenCalled(); + }); + it("leaves a completed local account saved when its host is cancelled", async () => { + let finish!: (result: { connectionId: string; grantId: string }) => void; + managedApi.connectLocal.mockImplementationOnce(() => new Promise(resolve => { finish = resolve; })); + const onComplete = vi.fn(); + await mount("claude_local", false, false, false, false, false, { intent: { provider: "anthropic", method: "subscription", name: "My account", ownership: "personal", agentIds: [], allAgents: false }, onComplete }, true); + openProvider(); click("Connect"); flushSync(() => root.unmount()); + finish({ connectionId: "saved", grantId: "saved-grant" }); + await new Promise(resolve => setTimeout(resolve, 0)); + expect(onComplete).not.toHaveBeenCalled(); + }); + it.each(["claude_local", "codex_local"] as const)("does not import local credentials for an unsupported remote environment: %s", async (adapterType) => { + const onComplete = vi.fn(); + const intent = { provider: adapterType === "claude_local" ? "anthropic" as const : "openai" as const, method: "subscription" as const, name: "Engineering subscription", ownership: "shared" as const, agentIds: ["nova"], allAgents: false }; + const { test } = await mount(adapterType, false, false, false, true, false, { intent, onComplete }); + openProvider(); + expect(host.textContent).toContain("This environment does not support browser sign-in"); + expect(host.textContent).not.toContain("login on this machine"); + click("Connect"); + expect(onComplete).not.toHaveBeenCalled(); + expect(managedApi.create).not.toHaveBeenCalled(); + expect(test).not.toHaveBeenCalled(); + }); + + it.each(["claude_local", "codex_local"] as const)("drives onboarding's provider redirect and completion: %s", async (adapterType) => { + const onComplete = vi.fn(); + const intent = { provider: adapterType === "claude_local" ? "anthropic" as const : "openai" as const, method: "subscription" as const, name: "My account", ownership: "personal" as const, agentIds: [], allAgents: false }; + await mount(adapterType, false, true, false, false, false, { intent, onComplete }); + openProvider(); + const panel = () => mocks.loginPanel.mock.calls.at(-1)![0]; + expect(panel().chrome).toBe("onboarding"); + expect(panel().autoStart).toBe(true); + expect(panel().aiConnection).toEqual(intent); + const open = vi.spyOn(window, "open").mockReturnValue(null); + try { + flushSync(() => panel().onPromptReady("https://provider.example/authorize")); + click(adapterType === "claude_local" ? "Sign in to Claude" : "Sign in to OpenAI"); + expect(open).toHaveBeenCalledWith("https://provider.example/authorize", "_blank", "noreferrer,noopener"); + expect(host.textContent).toContain("Waiting for code"); + flushSync(() => panel().onCodeSubmitted()); + expect(host.textContent).toContain("Connecting"); + flushSync(() => panel().onSubmitFailed()); + expect(host.textContent).toContain("Waiting for code"); + flushSync(() => panel().onConnected("session-1")); + await vi.waitFor(() => expect(onComplete).toHaveBeenCalledWith({ connectionId: "login-account", grantId: "login-grant", method: "subscription" })); + expect(managedApi.loginResult).toHaveBeenCalledWith("c1", "session-1"); + } finally { open.mockRestore(); } + }); + + it("does not advance after Back while the saved login result is loading", async () => { + let finish!: (result: { connectionId: string; grantId: string }) => void; + managedApi.loginResult.mockImplementationOnce(() => new Promise(resolve => { finish = resolve; })); + const onComplete = vi.fn(); + await mount("claude_local", false, true, false, false, false, { intent: { provider: "anthropic", method: "subscription", name: "My account", ownership: "personal", agentIds: [], allAgents: false }, onComplete }); + openProvider(); + flushSync(() => mocks.loginPanel.mock.calls.at(-1)![0].onConnected("session-1")); + click("Back"); + finish({ connectionId: "saved", grantId: "grant" }); + await Promise.resolve(); + expect(onComplete).not.toHaveBeenCalled(); + }); + it("starts the existing browser login when adding an account even if the environment is authenticated", async () => { + await mount("claude_local", true, true, false, true, false, { intent: { provider: "anthropic", method: "subscription", name: "My second account", ownership: "personal", agentIds: [], allAgents: false }, onComplete: vi.fn() }); + openProvider(); + expect(host.textContent).toContain("New subscription login"); + expect(host.textContent).not.toContain("Use saved subscription"); + }); + it("defaults to subscription when no saved credentials exist", async () => { await mount("claude_local", false, true, false, false); expect(host.textContent).toContain("Use API key instead"); @@ -241,4 +378,36 @@ describe("AgentProviderConnection reuse", () => { click("Connect"); await vi.waitFor(() => expect(test).toHaveBeenCalledWith({ env: {} })); }); + it.each(["claude_local", "codex_local"] as const)("uses a managed subscription through the upstream chooser for %s", async (adapterType) => { + const provider = adapterType === "claude_local" ? "anthropic" : "openai"; + managedApi.list.mockResolvedValue({ currentUserId: "user-1", connections: [{ + id: "account", grantId: "grant", companyId: "c1", provider, + method: "subscription", name: "My subscription", ownership: "personal", + ownerUserId: "user-1", isDefault: true, status: "connected", + }] } as never); + const { connected } = await mount(adapterType, false, true, false, false); + openProvider(); + expect(host.querySelector('select[aria-label="Saved subscription"]')?.textContent).toContain("My subscription (Your default)"); + click("Use saved subscription"); + await vi.waitFor(() => expect(connected).toHaveBeenCalledWith({ env: {}, aiConnection: { provider, method: "subscription", mode: "responsible_user" } })); + expect(managedApi.create).not.toHaveBeenCalled(); + flushSync(() => { + const select = host.querySelector("select")!; + select.value = ""; + select.dispatchEvent(new Event("change", { bubbles: true })); + }); + expect(host.textContent).toContain("New subscription login"); + await client.invalidateQueries(); + await vi.waitFor(() => expect(client.isFetching()).toBe(0)); + expect(host.querySelector("select")!.value).toBe(""); + expect(host.textContent).toContain("New subscription login"); + }); + it("never offers a saved Codex home in Claude's subscription chooser", async () => { + await mount("claude_local", false, true, true); + click("Use subscription instead"); + openProvider(); + expect(host.querySelector('select[aria-label="Saved subscription"]')).toBeNull(); + expect(host.textContent).not.toContain("ChatGPT account"); + }); + }); diff --git a/ui/src/components/new-agent/AgentProviderConnection.tsx b/ui/src/components/new-agent/AgentProviderConnection.tsx index 6faa67a3e7..d51b1c0422 100644 --- a/ui/src/components/new-agent/AgentProviderConnection.tsx +++ b/ui/src/components/new-agent/AgentProviderConnection.tsx @@ -1,3 +1,7 @@ +import { healthApi } from "@/api/health"; +import { aiConnectionsApi } from "@/api/ai-connections"; +import { useLocalAiLogin } from "../ai-connections/useLocalAiLogin"; +import type { AiConnectionBinding, AiConnectionLoginIntent } from "@paperclipai/shared"; import { useEffect, useRef, useState } from "react"; import { useQuery } from "@tanstack/react-query"; import { motion } from "motion/react"; @@ -9,6 +13,7 @@ import { agentsApi } from "@/api/agents"; import { queryKeys } from "@/lib/queryKeys"; import { AdapterLoginPanel } from "../AgentConfigForm"; import { + LocalProviderLoginInstructions, OnboardingCardField, OnboardingLoginCard, } from "../AdapterLoginChrome"; @@ -21,6 +26,7 @@ import type { EnvBinding } from "@paperclipai/shared"; export type ProviderConnection = { env: Record; + aiConnection?: AiConnectionBinding; /** Kept in memory until the user finishes setup. */ credentials?: Record; storedSessionId?: string; @@ -31,20 +37,33 @@ export function AgentProviderConnection({ adapterType, environmentId, canLogin, + localEnvironment = false, onConnected, onBack, testConnection, testError, + managedAccount, }: { companyId: string; - adapterType: "claude_local" | "codex_local"; + adapterType: "claude_local" | "codex_local" | "grok_local"; environmentId: string | null; canLogin: boolean; + localEnvironment?: boolean; onConnected: (connection: ProviderConnection) => void; onBack: () => void; testConnection: (connection: ProviderConnection) => Promise; testError?: string | null; + /** Connections supplies its access intent; presentation and login controllers stay shared. */ + managedAccount?: { + intent: AiConnectionLoginIntent; + initialMethod?: "subscription" | "api_key"; + fixedMethod?: boolean; + disabled?: boolean; + onComplete: (result: { connectionId: string; grantId: string; method: "subscription" | "api_key" }) => void; + }; }) { + const health = useQuery({ queryKey: queryKeys.health, queryFn: healthApi.get, enabled: localEnvironment }); + const canUseLocalLogin = localEnvironment && (health.data?.localAiLoginSupported ?? health.data?.deploymentMode === "local_trusted"); const epoch = useRef(0); useEffect( () => () => { @@ -56,21 +75,32 @@ export function AgentProviderConnection({ epoch.current++; setBusy(false); setOpened(false); + setAuthorizationUrl(null); + setLoginPhase("preparing"); }; - const [methodChoice, setMethod] = useState<"subscription" | "api" | null>(null); + const [methodChoice, setMethod] = useState<"subscription" | "api" | null>(managedAccount?.initialMethod === "api_key" ? "api" : managedAccount ? "subscription" : null); const [opened, setOpened] = useState(false); + const [authorizationUrl, setAuthorizationUrl] = useState(null); + const [loginPhase, setLoginPhase] = useState<"preparing" | "ready" | "waiting" | "connecting">("preparing"); + const phaseBeforeSubmit = useRef<"ready" | "waiting">("ready"); const [apiKey, setApiKey] = useState(""); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); const [storedConnection, setStoredConnection] = useState(null); - const provider = adapterType === "claude_local" ? "Claude" : "OpenAI"; + const provider = adapterType === "claude_local" ? "Claude" : adapterType === "grok_local" ? "Grok" : "OpenAI"; const envKey = - adapterType === "claude_local" ? "ANTHROPIC_API_KEY" : "OPENAI_API_KEY"; - const savedKeys = useSavedProviderKeys(companyId, envKey); + adapterType === "claude_local" ? "ANTHROPIC_API_KEY" : adapterType === "grok_local" ? "XAI_API_KEY" : "OPENAI_API_KEY"; + const aiProvider = adapterType === "claude_local" ? "anthropic" : adapterType === "grok_local" ? "xai" : "openai"; + const availableKeys = useSavedProviderKeys(companyId, envKey); + // Add/reconnect creates the requested account, never copies a saved account's + // credential or silently changes its ownership. Agent setup retains reuse. + const savedKeys = managedAccount + ? { ...availableKeys, options: [], subscriptions: [], loading: false } + : availableKeys; const [subscriptionId, setSubscriptionId] = useState(null); const savedSubscription = - adapterType === "codex_local" + savedKeys.subscriptions.length ? savedKeys.subscriptions.find( (option) => option.id === (subscriptionId ?? savedKeys.subscriptions[0]?.id), @@ -80,11 +110,19 @@ export function AgentProviderConnection({ const selectedKey = savedKeys.options.find( (option) => option.id === (selectedKeyId ?? savedKeys.options[0]?.id), ); - const storedLogin = savedKeys.storedLogin; + const storedLogin = managedAccount + ? { ...savedKeys.storedLogin, data: undefined, isPending: false, isError: false } + : savedKeys.storedLogin; + const savedManagedAccount = useRef<{ connectionId: string; grantId: string } | null>(null); const method = methodChoice ?? ( - (adapterType === "claude_local" ? storedLogin.data : savedKeys.subscriptions.length) + (savedKeys.subscriptions.length > 0 || (adapterType === "claude_local" && !savedSubscription && storedLogin.data)) ? "subscription" : savedKeys.options.length ? "api" : "subscription" ); + const localLogin = useLocalAiLogin(companyId, managedAccount?.intent ?? { + provider: aiProvider, method: "subscription", name: `My ${provider} subscription`, + ownership: "personal", agentIds: [], allAgents: true, + }, canUseLocalLogin && method === "subscription" && !savedSubscription && !storedLogin.data, + { allowHostClaude: health.data?.deploymentMode === "local_trusted" }); const auth = useQuery({ queryKey: queryKeys.agents.authSignal( companyId, @@ -98,32 +136,52 @@ export function AgentProviderConnection({ environmentId ?? undefined, ), retry: false, + enabled: !managedAccount, }); async function connect() { - if (busy) return; + if (busy || managedAccount?.disabled) return; const run = ++epoch.current; setBusy(true); setError(null); try { - const connection = + if (managedAccount) { + if (method === "subscription" && !canUseLocalLogin) return; + const result = savedManagedAccount.current ?? await (method === "api" + ? aiConnectionsApi.create(companyId, { ...managedAccount.intent, method: "api_key", apiKey: apiKey.trim() }) + : localLogin.connect(managedAccount.intent)); + savedManagedAccount.current = result; + setApiKey(""); + if (run === epoch.current) managedAccount.onComplete({ ...result, method: method === "api" ? "api_key" : "subscription" }); + return; + } + let connection: ProviderConnection = method === "api" ? selectedKey - ? { env: { [envKey]: selectedKey.binding } } + ? selectedKey.aiConnection ? { env: {}, aiConnection: selectedKey.aiConnection } : { env: { [envKey]: selectedKey.binding } } : (storedConnection ?? { env: {}, credentials: { [envKey]: apiKey.trim() }, }) : { - env: savedSubscription + ...(savedSubscription?.aiConnection ? { aiConnection: savedSubscription.aiConnection } : {}), + env: savedSubscription?.binding ? { CODEX_HOME: savedSubscription.binding } : {}, - ...(adapterType === "claude_local" && storedLogin.data + ...(adapterType === "claude_local" && !savedSubscription && storedLogin.data ? { env: buildFixedClaudeOAuthBinding(), applyStoredClaudeLogin: true, } : {}), }; + if (method === "subscription" && canUseLocalLogin && !savedSubscription && !storedLogin.data) { + savedManagedAccount.current ??= await localLogin.connect(); + connection = { env: {}, aiConnection: { provider: aiProvider, method: "subscription", mode: "responsible_user" } }; + } + if (connection.credentials) { + await aiConnectionsApi.create(companyId, { provider: aiProvider, method: "api_key", name: `My ${provider} API`, ownership: "personal", apiKey: connection.credentials[envKey], agentIds: [], allAgents: true }); + connection = { env: {}, aiConnection: { provider: aiProvider, method: "api_key", mode: "responsible_user" } }; + } if (run !== epoch.current) return; if (method === "api") { setApiKey(""); @@ -138,6 +196,7 @@ export function AgentProviderConnection({ ); } catch (cause) { if (run !== epoch.current) return; + if (managedAccount) setApiKey(""); setError( cause instanceof Error ? cause.message @@ -154,9 +213,9 @@ export function AgentProviderConnection({ !savedSubscription && !savedKeys.loading && !storedLogin.data && - (auth.data?.status !== "present" || subscriptionId === ""); + (Boolean(managedAccount) || auth.data?.status !== "present" || subscriptionId === ""); return ( -
+
@@ -175,13 +234,14 @@ export function AgentProviderConnection({ mode={method} selectedId={opened ? adapterType : null} collapsed={opened} - onSelect={() => setOpened(true)} + onSelect={() => { if (!managedAccount?.disabled) setOpened(true); }} /> - {!opened && ( + {!opened && !managedAccount?.fixedMethod && (
{ + savedManagedAccount.current = null; setMethod(next); setError(null); }} @@ -195,7 +255,6 @@ export function AgentProviderConnection({

)} {method === "subscription" && - adapterType === "codex_local" && savedKeys.subscriptions.length > 0 && ( { - const connection = { - env: buildFixedClaudeOAuthBinding(), - storedSessionId, - }; + onStored={() => {}} + onPromptReady={(url) => { + setAuthorizationUrl(url); + setLoginPhase((phase) => url ? (phase === "preparing" ? "ready" : phase) : "preparing"); + }} + onCodeSubmitted={() => { + phaseBeforeSubmit.current = loginPhase === "waiting" ? "waiting" : "ready"; + setLoginPhase("connecting"); + }} + onSubmitFailed={() => { + setLoginPhase((phase) => phase === "connecting" ? phaseBeforeSubmit.current : phase); + }} + onConnected={(sessionId) => { + if (managedAccount) { + if (!sessionId) { setError("The login did not return a saved connection. Try again."); return; } + const run = epoch.current; + setLoginPhase("connecting"); + void aiConnectionsApi.loginResult(companyId, sessionId).then((result) => { + if (run === epoch.current) managedAccount.onComplete({ ...result, method: "subscription" }); + }).catch(() => { + if (run !== epoch.current) return; + setLoginPhase("ready"); + setError("Could not retrieve the saved connection. Go back and retry."); + }); + return; + } + const connection: ProviderConnection = { env: {}, aiConnection: { provider: aiProvider, method: "subscription", mode: "responsible_user" } }; setStoredConnection(connection); onConnected(connection); }} - onConnected={() => { - if (adapterType === "codex_local") onConnected({ env: {} }); - }} /> - ) : savedSubscription ? null : ( + ) : savedSubscription ? null : canUseLocalLogin && !storedLogin.data ? ( + { setError(null); localLogin.retry(); } }} /> + ) : (

{storedLogin.data ? "Use your saved Claude subscription for this agent." : canLogin ? "Use the existing provider connection for this environment." - : `Use the ${provider} login on this machine. If you haven’t signed in yet, run ${adapterType === "claude_local" ? "claude auth login" : "codex login"} in your terminal, then connect.`} + : "This environment does not support browser sign-in. Choose a sign-in environment or connect with an API key."}

)}
@@ -296,13 +377,20 @@ export function AgentProviderConnection({ {testError ?? error}

)} + {localEnvironment && health.isError && ( +

Could not prepare sign-in. Reload this page to try again.

+ )} { if (opened) cancel(); else onBack(); }} primaryLabel={ - busy + opened && needsLogin + ? loginPhase === "waiting" ? "Waiting for code" + : loginPhase === "connecting" ? "Connecting" + : `Sign in to ${provider}` + : busy ? "Connecting" : method === "subscription" && (storedLogin.data || savedSubscription) @@ -312,18 +400,28 @@ export function AgentProviderConnection({ : "Connect" } primaryDisabled={ - auth.isPending || + managedAccount?.disabled || + (Boolean(managedAccount) && method === "subscription" && !canLogin && !canUseLocalLogin) || + (localEnvironment && health.isPending) || localLogin.preparing || Boolean(localLogin.error) || + (!managedAccount && auth.isPending) || savedKeys.loading || (adapterType === "claude_local" && storedLogin.isPending) || !opened || - Boolean(needsLogin) || + (Boolean(needsLogin) && (!authorizationUrl || loginPhase !== "ready")) || (method === "api" && !apiKey.trim() && !storedConnection && !selectedKey) } loading={busy} - onPrimary={() => void connect()} + primaryIcon={opened && needsLogin ? loginPhase === "ready" ? "none" : "spinner" : undefined} + onPrimary={() => { + if (needsLogin) { + if (!authorizationUrl || loginPhase !== "ready") return; + window.open(authorizationUrl, "_blank", "noreferrer,noopener"); + setLoginPhase("waiting"); + } else void connect(); + }} />
); diff --git a/ui/src/components/new-agent/NewAgentSetup.tsx b/ui/src/components/new-agent/NewAgentSetup.tsx index 9c3aaa8999..6ca7939772 100644 --- a/ui/src/components/new-agent/NewAgentSetup.tsx +++ b/ui/src/components/new-agent/NewAgentSetup.tsx @@ -1,3 +1,5 @@ +import { AiConnectionField, aiProviderForAdapter } from "../ai-connections/AiConnectionField"; +import type { AiConnectionBinding } from "@paperclipai/shared"; import { DEFAULT_CODEX_LOCAL_MODEL } from "@paperclipai/adapter-codex-local"; import { SETUP_CREDENTIAL_KEYS, @@ -114,7 +116,7 @@ function Setup({ : "codex_local" : adapterType; const connectionAdapter = - brandType === "claude_local" || brandType === "codex_local" + brandType === "claude_local" || brandType === "codex_local" || brandType === "grok_local" ? brandType : null; const multiProvider = @@ -141,7 +143,13 @@ function Setup({ const [providerBinding, setProviderBinding] = useState( null, ); + const [runtimeAiBinding, setRuntimeAiBinding] = useState(() => + brandType === "opencode_local" + ? { provider: "openrouter", method: "api_key", mode: "responsible_user" } + : undefined, + ); const [connection, setConnection] = useState(null); + const aiBinding = runtimeAiBinding ?? connection?.aiConnection; const [repository, setRepository] = useState(""); const [branch, setBranch] = useState(""); const [createdInSession, setCreated] = useState(null); @@ -203,8 +211,8 @@ function Setup({ queryFn: () => environmentsApi.capabilities(companyId), }); const models = useQuery({ - queryKey: queryKeys.agents.adapterModels(companyId, brandType), - queryFn: () => agentsApi.adapterModels(companyId, brandType), + queryKey: queryKeys.agents.adapterModels(companyId, brandType, null, aiBinding?.provider), + queryFn: () => agentsApi.adapterModels(companyId, brandType, { provider: aiBinding?.provider }), enabled: Boolean(brandType) && showModel, retry: false, }); @@ -343,7 +351,7 @@ function Setup({ ...(runnerProvider === "claude" ? { acpxAgent: "claude" } : {}), ...(model ? { model } : {}), }); - if (hasCredentialField && binding) { + if (!aiBinding && !nextConnection?.aiConnection && hasCredentialField && binding) { if (adapterType === "hermes_gateway") config.apiKey = binding; else config.env = { ...((config.env as object) ?? {}), [envKey]: binding }; @@ -401,6 +409,7 @@ function Setup({ return buildConfig(nextConnection); } function pendingCredentials(nextConnection = connection) { + if (aiBinding || nextConnection?.aiConnection) return {}; return { ...nextConnection?.credentials, ...(hasCredentialField && apiKey.trim() @@ -423,6 +432,7 @@ function Setup({ providerAdapter: brandType, adapterConfig: config, testCredentials: pendingCredentials(nextConnection), + aiConnection: runtimeAiBinding ?? nextConnection?.aiConnection, environmentId, }); if (run !== generation.current) return false; @@ -494,7 +504,7 @@ function Setup({ defaultEnvironmentId: environmentOverride || (forced.forced || managedOnly ? environmentId : null), - runtimeConfig: buildNewAgentRuntimeConfig({ heartbeatEnabled: false }), + runtimeConfig: { ...buildNewAgentRuntimeConfig({ heartbeatEnabled: false }), ...(aiBinding ? { aiConnection: aiBinding } : {}) }, budgetMonthlyCents: 0, ...(connection?.storedSessionId ? { storedSessionId: connection.storedSessionId } @@ -700,7 +710,7 @@ function Setup({
@@ -710,6 +720,7 @@ function Setup({ adapterType={connectionAdapter} environmentId={environmentId} canLogin={canLogin} + localEnvironment={environment?.driver === "local"} onBack={() => navigate("/agents/all")} testConnection={runTest} testError={ @@ -758,7 +769,7 @@ function Setup({

{created.status === "pending_approval" ? "An organization administrator must approve this agent before it can work." - : "Your agent has not started running."} + : "Assign a task when you’re ready for this agent to work."}

@@ -797,6 +808,9 @@ function Setup({

Runtime

+ {aiProviderForAdapter(brandType) && { setRuntimeAiBinding(binding); resetTest(); }} />} + {models.error &&

Could not load models. Retry or enter a model ID manually.

} {((showModel && !usingKimiApi) || efforts.length > 0) && (
@@ -866,7 +880,7 @@ function Setup({ manually.

)} - {hasCredentialField && ( + {hasCredentialField && !aiBinding && (
{chooseProvider && ( diff --git a/ui/src/components/onboarding/SavedProviderKeySelect.tsx b/ui/src/components/onboarding/SavedProviderKeySelect.tsx index 4ee6fa265f..0cc500c158 100644 --- a/ui/src/components/onboarding/SavedProviderKeySelect.tsx +++ b/ui/src/components/onboarding/SavedProviderKeySelect.tsx @@ -1,3 +1,5 @@ +import { aiConnectionsApi } from "@/api/ai-connections"; +import type { AiProvider } from "@paperclipai/shared"; import { useQuery } from "@tanstack/react-query"; import { agentsApi } from "@/api/agents"; import { ApiError } from "@/api/client"; @@ -5,6 +7,7 @@ import { secretsApi } from "@/api/secrets"; import { queryKeys } from "@/lib/queryKeys"; import { savedProviderKeys, + savedManagedProviderAccounts, savedCodexSubscriptions, type SavedProviderKey, } from "@/lib/saved-provider-credentials"; @@ -14,6 +17,14 @@ export function useSavedProviderKeys( envKey: string, enabled = true, ) { + const provider = ({ ANTHROPIC_API_KEY: "anthropic", OPENAI_API_KEY: "openai", OPENROUTER_API_KEY: "openrouter", XAI_API_KEY: "xai" } as Record)[envKey]; + const managed = useQuery({ + queryKey: ["ai-connections", companyId], + queryFn: () => aiConnectionsApi.list(companyId!), + enabled: Boolean(companyId && provider) && enabled, + retry: false, + }); + const managedAccounts = provider && managed.data ? savedManagedProviderAccounts(companyId!, provider, managed.data.currentUserId, managed.data.connections) : []; const personal = useQuery({ queryKey: queryKeys.secrets.myUserSecrets(companyId ?? ""), queryFn: () => secretsApi.listMyUserSecrets(companyId!), @@ -43,19 +54,19 @@ export function useSavedProviderKeys( }); return { storedLogin, - options: savedProviderKeys( + options: [...managedAccounts.filter(account => account.aiConnection?.method === "api_key"), ...savedProviderKeys( companyId ?? "", envKey, personal.data ?? [], organization.data ?? [], - ), - subscriptions: savedCodexSubscriptions( + )], + subscriptions: [...managedAccounts.filter(account => account.aiConnection?.method === "subscription"), ...(provider === "openai" ? savedCodexSubscriptions( companyId ?? "", organization.data ?? [], - ), + ) : [])], // Background refreshes must not unmount an active login panel sharing this query. - loading: personal.isLoading || organization.isLoading || storedLogin.isLoading, - error: personal.isError || organization.isError, + loading: personal.isLoading || organization.isLoading || storedLogin.isLoading || managed.isLoading, + error: personal.isError || organization.isError || managed.isError, }; } diff --git a/ui/src/components/task-chat/TaskChatBubble.test.tsx b/ui/src/components/task-chat/TaskChatBubble.test.tsx index 50bd0d138f..00d8746155 100644 --- a/ui/src/components/task-chat/TaskChatBubble.test.tsx +++ b/ui/src/components/task-chat/TaskChatBubble.test.tsx @@ -41,6 +41,19 @@ describe("TaskChatBubble attachment chips", () => { ); } + it("shows persistent iMessage attribution only on inbound human bubbles", () => { + for (const author of ["human", "agent"] as const) { + flushSync(() => root!.render( + + + , + )); + expect(container.textContent?.includes("Sent from iMessage")).toBe(author === "human"); + } + renderMessage("Board reply"); + expect(container.textContent).not.toContain("Sent from iMessage"); + }); + it("opens attachment images in the shared task gallery", () => { const openGallery = vi.fn(() => true); const contentPath = "/api/attachments/shared-image/content"; diff --git a/ui/src/components/task-chat/TaskChatBubble.tsx b/ui/src/components/task-chat/TaskChatBubble.tsx index 1c34011a62..3caae25934 100644 --- a/ui/src/components/task-chat/TaskChatBubble.tsx +++ b/ui/src/components/task-chat/TaskChatBubble.tsx @@ -186,6 +186,7 @@ function TaskChatBubbleContent({ } const isHuman = item.author === "human"; + const sentFromIMessage = isHuman && item.sourceChannel === "imessage-photon"; // Non-image file references ("[name](/api/attachments/…/content)") render as // attachment chips under the bubble; link-only lines leave the body text. const { refs: linkedRefs, text: bodyWithoutAttachmentLinks } = @@ -418,9 +419,11 @@ function TaskChatBubbleContent({ ) : null}
) - ) : item.timestamp ? ( + ) : item.timestamp || sentFromIMessage ? ( // Timestamps are always visible (round 9) — no longer hover-revealed. + {sentFromIMessage ? "Sent from iMessage" : null} + {sentFromIMessage && item.timestamp ? " · " : null} {item.timestamp} ) : null} diff --git a/ui/src/components/task-chat/TaskChatQueuedMessages.test.tsx b/ui/src/components/task-chat/TaskChatQueuedMessages.test.tsx index 16e03e8457..929020b131 100644 --- a/ui/src/components/task-chat/TaskChatQueuedMessages.test.tsx +++ b/ui/src/components/task-chat/TaskChatQueuedMessages.test.tsx @@ -293,11 +293,12 @@ describe("TaskChatQueuedMessages", () => { ).toBeNull(); }); - it("uses interrupt instead of steer for legacy runners and keeps the row queued", async () => { + it.each(["run-1", null])("delivers legacy queued messages with target %s", async (targetRunId) => { const onInterrupt = vi.fn().mockResolvedValue(undefined); render({ queue: { ...queue, + targetRunId, protocol: "legacy", steeringDisposition: "unsupported", }, @@ -324,7 +325,7 @@ describe("TaskChatQueuedMessages", () => { ), ).not.toBeNull(); expect(container.textContent).toContain( - "Interruption requested. Queued messages will continue after the active turn stops.", + "Queued messages will be sent when the previous run has stopped.", ); }); }); diff --git a/ui/src/components/task-chat/TaskChatQueuedMessages.tsx b/ui/src/components/task-chat/TaskChatQueuedMessages.tsx index 286d64b976..2562430f75 100644 --- a/ui/src/components/task-chat/TaskChatQueuedMessages.tsx +++ b/ui/src/components/task-chat/TaskChatQueuedMessages.tsx @@ -146,8 +146,8 @@ function SortableQueuedMessage({ )} +
; +} diff --git a/ui/src/features/connections/ConnectionIntentInteractionBody.test.tsx b/ui/src/features/connections/ConnectionIntentInteractionBody.test.tsx index 0335b8bfa1..e64baea43c 100644 --- a/ui/src/features/connections/ConnectionIntentInteractionBody.test.tsx +++ b/ui/src/features/connections/ConnectionIntentInteractionBody.test.tsx @@ -16,6 +16,7 @@ import { } from "@/fixtures/issueThreadInteractionFixtures"; import { ConnectionIntentInteractionBody } from "./ConnectionIntentInteractionBody"; +const credentialRender = vi.hoisted(() => vi.fn()); const setupOptionsMock = vi.hoisted(() => vi.fn()); const completeMock = vi.hoisted(() => vi.fn()); const declineMock = vi.hoisted(() => vi.fn()); @@ -30,6 +31,14 @@ vi.mock("@/api/connection-intents", () => ({ }, })); +vi.mock("@/components/ai-connections/AiConnectionCredentialStep", () => ({ + AiConnectionCredentialStep: (props: { connectionId?: string; name: string; fixedMethod?: boolean; onComplete: (result: {connectionId: string; grantId: string; method: "api_key"}) => void; onCancel: () => void }) => { credentialRender(props); return
+ {props.name}{String(props.fixedMethod)} + + +
; }, +})); + vi.mock("./ConnectionSetupFlow", () => ({ ConnectionSetupFlow: (props: { requestedAgentId?: string; @@ -402,3 +411,54 @@ describe("ConnectionIntentInteractionBody dialog behavior", () => { ); }); }); + + +describe("AI repair inside the card", () => { + const interaction: ConnectionIntentInteraction = { ...pendingConnectionIntentInteraction, payload: { ...pendingConnectionIntentInteraction.payload, purpose: "ai" } }; + const connection = { id: "selected-account", name: "My Codex account", provider: "openai", method: "api_key", ownership: "personal", ownerName: "Dotta", status: "revoked" }; + it("reuses authentication inline, preserves the selected account, cancels with focus, and completes", async () => { + setupOptionsMock.mockResolvedValue({ interaction, existingConnections: [], aiRepair: { connection, canReconnect: true } }); + completeMock.mockResolvedValue({ ...interaction, status: "accepted" }); + renderBody(interaction); + await flush(); + await act(() => button("Fix connection")!.click()); + expect(document.querySelector('[role="dialog"]')).toBeNull(); + expect(document.querySelector('[data-testid="ai-connection-inline-repair"]')?.textContent).toContain("My Codex account"); + await act(() => button("Cancel repair")!.click()); + await waitForAssertion(() => expect(document.activeElement?.getAttribute("data-testid")).toBe("connection-intent-focus-target")); + expect(completeMock).not.toHaveBeenCalled(); + await act(() => button("Fix connection")!.click()); + await act(() => button("Reconnect selected account")!.click()); + expect(completeMock).toHaveBeenCalledWith(interaction.id, "selected-account"); + }); + it("keeps a late credential save after cancellation from accepting the request", async () => { + setupOptionsMock.mockResolvedValue({ interaction, existingConnections: [], aiRepair: { connection, canReconnect: true } }); + renderBody(interaction); await flush(); + await act(() => button("Fix connection")!.click()); + const abandoned = credentialRender.mock.lastCall![0]; + await act(() => button("Cancel repair")!.click()); + await act(() => button("Fix connection")!.click()); + await act(() => abandoned.onComplete({ connectionId: connection.id, grantId: "grant", method: "api_key" })); + expect(completeMock).not.toHaveBeenCalled(); + }); + it("offers continuation for the restored account without another login", async () => { + setupOptionsMock.mockResolvedValue({ interaction, existingConnections: [connection], aiRepair: { connection, canReconnect: true } }); + renderBody(interaction); await flush(); + await act(() => button("Fix connection")!.click()); + expect(document.querySelector('[data-testid="shared-ai-credentials"]')).toBeNull(); + await act(() => button("Continue task")!.click()); + expect(completeMock).toHaveBeenCalledWith(interaction.id, connection.id); + }); + it("does not let another user reconnect the owner's account", async () => { + setupOptionsMock.mockResolvedValue({ interaction, existingConnections: [], aiRepair: { connection, canReconnect: false } }); + renderBody(interaction); await flush(); + await act(() => button("Fix connection")!.click()); + expect(document.body.textContent).toContain("Dotta must reconnect My Codex account"); + expect(document.querySelector('[data-testid="shared-ai-credentials"]')).toBeNull(); + }); + it("does not promise to run without credentials when declined", () => { + renderBody({ ...interaction, status: "rejected" }); + expect(document.body.textContent).toContain("The task still needs a working AI connection"); + expect(document.body.textContent).not.toContain("can continue without it"); + }); +}); diff --git a/ui/src/features/connections/ConnectionIntentInteractionBody.tsx b/ui/src/features/connections/ConnectionIntentInteractionBody.tsx index 69601efa62..7fd9e5254a 100644 --- a/ui/src/features/connections/ConnectionIntentInteractionBody.tsx +++ b/ui/src/features/connections/ConnectionIntentInteractionBody.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useRef, useState, type ReactNode } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { CheckCircle2, @@ -10,6 +10,7 @@ import { } from "lucide-react"; import type { ConnectionIntentInteraction } from "@paperclipai/shared"; import { connectionIntentsApi } from "@/api/connection-intents"; +import { AiConnectionCredentialStep } from "@/components/ai-connections/AiConnectionCredentialStep"; import { AppLogo } from "@/pages/apps/AppLogo"; import { Button } from "@/components/ui/button"; import { @@ -23,26 +24,36 @@ import { import { ConnectionSetupFlow, type ConnectionSetupCompletion, + type ConnectionSetupFlowProps, } from "./ConnectionSetupFlow"; export interface ConnectionIntentInteractionBodyProps { interaction: ConnectionIntentInteraction; currentUserId?: string | null; addresseeLabel: string; + renderSetup?: (props: ConnectionSetupFlowProps) => ReactNode; } export function ConnectionIntentInteractionBody({ interaction, currentUserId, addresseeLabel, + renderSetup, }: ConnectionIntentInteractionBodyProps) { const [open, setOpen] = useState(false); const focusTargetRef = useRef(null); + const setupGeneration = useRef(0); + const generation = setupGeneration.current; + const closeSetup = () => { + setupGeneration.current += 1; + setOpen(false); + }; const queryClient = useQueryClient(); const isAddressee = Boolean( currentUserId && interaction.addresseeUserId === currentUserId, ); const isPending = interaction.status === "pending"; + const isAi = interaction.payload.purpose === "ai"; const focusTargetId = `connection-intent-focus-target-${interaction.id}`; const invalidateTask = async ( @@ -131,6 +142,12 @@ export function ConnectionIntentInteractionBody({ ); const finishNewConnection = async (completion: ConnectionSetupCompletion) => { + // A completed credential save survives cancellation, but an abandoned form + // must not accept the task request (even if a new form has since opened). + if (isAi && generation !== setupGeneration.current) { + await setupQuery.refetch(); + return; + } if (completion.resolvedByCallback) { // A browser message cannot establish authorization. Read the durable result. const verified = await setupQuery.refetch(); @@ -143,19 +160,34 @@ export function ConnectionIntentInteractionBody({ completeMutation.mutate(completion.connectionId); }; + const setupProps: ConnectionSetupFlowProps | null = setupQuery.data ? { + host: "dialog", + serviceSlug: interaction.payload.serviceSlug.startsWith("connection:") ? undefined : interaction.payload.serviceSlug, + configuredConnection: interaction.payload.serviceSlug.startsWith("connection:") ? setupQuery.data.existingConnections[0] : undefined, + requestedAgentId: setupQuery.data.requestedAgentId, + aiConnection: setupQuery.data.aiConnection, + interactionId: interaction.id, + existingConnections: setupQuery.data.existingConnections, + onUseExisting: async (connectionId) => { await completeMutation.mutateAsync(connectionId); }, + onComplete: (completion) => { void finishNewConnection(completion); }, + onOAuthDeclined: () => declineMutation.mutate(), + onPhaseChange: handlePhaseChange, + onCancel: () => { closeSetup(); returnFocusToCard(); }, + } : null; + const resultOutcome = interaction.result?.outcome; const status = interaction.status === "accepted" ? { icon: CheckCircle2, title: `${interaction.payload.serviceName} connected`, - body: `${interaction.payload.requestingAgentName} can use this connection on the continuation run.`, + body: isAi ? "The connection was restored for this request." : `${interaction.payload.requestingAgentName} can use this connection on the continuation run.`, } : interaction.status === "rejected" ? { icon: XCircle, title: "Connection declined", - body: `${interaction.payload.requestingAgentName} was notified and can continue without it.`, + body: isAi ? "The task still needs a working AI connection before it can run." : `${interaction.payload.requestingAgentName} was notified and can continue without it.`, } : interaction.status === "expired" ? { @@ -224,6 +256,61 @@ export function ConnectionIntentInteractionBody({ const needsRetry = interaction.payload.phase === "needs_retry"; const authorizing = interaction.payload.phase === "authorizing"; + const repair = setupQuery.data?.aiRepair; + const selectedReady = repair && setupQuery.data?.existingConnections.some((connection) => connection.id === repair.connection.id); + const setupContent = setupQuery.isLoading ? ( +
+ Loading + connection options… +
+ ) : setupQuery.isError ? ( +
+

+ Couldn’t load connection setup +

+

+ {setupQuery.error instanceof Error + ? setupQuery.error.message + : "Try again."} +

+ +
+ ) : setupProps ? ( + renderSetup ? renderSetup(setupProps) : + ) : null; + const inlineContent = setupQuery.isLoading || setupQuery.isError ? setupContent + : selectedReady ?
+

{repair.connection.name} is ready.

+ +
+ : repair ? repair.canReconnect ? { void finishNewConnection(result); }} + onCancel={() => { closeSetup(); returnFocusToCard(); }} + /> :

+ {repair.connection.ownership === "personal" ? `${repair.connection.ownerName ?? "The account owner"} must reconnect ${repair.connection.name}.` : `The account owner must reconnect ${repair.connection.name}.`} + {" "}You can continue here once it is restored. +

+ : setupQuery.data?.aiConnection && setupQuery.data.aiConnection.mode !== "responsible_user" + ?

The selected account is no longer available to you. Ask its owner to restore access, or choose an available AI connection in the agent’s settings.

+ : setupContent; + return (

- {interaction.payload.requestingAgentName} needs{" "} - {interaction.payload.serviceName} + {isAi ? "AI connection needs attention" : `${interaction.payload.requestingAgentName} needs ${interaction.payload.serviceName}`}

- Connect your identity or reuse an eligible connection. Access is - added only for this agent. + {interaction.payload.purpose === "ai" + ? "Restore the agent’s selected AI account, then continue this task." + : "Connect your identity or reuse an eligible connection. Access is added only for this agent."}

@@ -260,15 +347,17 @@ export function ConnectionIntentInteractionBody({ ) : null}
- - + } + {isAi ? : -
- ) : setupQuery.data ? ( - { - await completeMutation.mutateAsync(connectionId); - }} - onComplete={(completion) => { - void finishNewConnection(completion); - }} - onOAuthDeclined={() => declineMutation.mutate()} - onPhaseChange={handlePhaseChange} - onCancel={() => setOpen(false)} - /> - ) : null} + {setupContent} - + }
+ {isAi && open ?
{inlineContent}
: null} {completeMutation.isError || declineMutation.isError || diff --git a/ui/src/features/connections/ConnectionSetupFlow.tsx b/ui/src/features/connections/ConnectionSetupFlow.tsx index 3a5b298536..babbf0523a 100644 --- a/ui/src/features/connections/ConnectionSetupFlow.tsx +++ b/ui/src/features/connections/ConnectionSetupFlow.tsx @@ -1,4 +1,6 @@ -import { useCallback, useEffect, useId, useMemo, useRef, useState } from "react"; +import { AiConnectionCredentialStep } from "@/components/ai-connections/AiConnectionCredentialStep"; +import { ConnectionChoiceList } from "./ConnectionChoiceList"; +import { useCallback, useEffect, useId, useMemo, useRef, useState, type ReactNode } from "react"; import { useMutation, useQuery } from "@tanstack/react-query"; import { ArrowUpRight, @@ -32,6 +34,7 @@ import type { ToolOAuthStartResult, } from "@paperclipai/shared"; import { + aiConnectionMetadataSchema, connectionMethodAcceptsCustomerOAuthClient, connectionMethodRequiresConfiguration, connectionMethodSupportsAutomaticOAuth, @@ -276,6 +279,7 @@ function appConnectHref( resumeConnectionId?: string | null; reconnectConnectionId?: string | null; interactionId?: string | null; + connectionMethodKey?: string | null; }, ): string { const stage = ROUTE_STAGE_BY_STEP[step] ?? "setup"; @@ -283,6 +287,7 @@ function appConnectHref( if (existing?.resumeConnectionId) params.set("resume", existing.resumeConnectionId); if (existing?.reconnectConnectionId) params.set("reconnect", existing.reconnectConnectionId); if (existing?.interactionId) params.set("intent", existing.interactionId); + if (existing?.connectionMethodKey) params.set("method", existing.connectionMethodKey); const path = credentialSource === "vercel_connect" ? "/apps/vercel-connect" : "/apps/connect"; return `${path}?${params.toString()}`; } @@ -364,7 +369,7 @@ function availableToolConnectionMethods( entry: AppDefinition, ): ConnectionMethodDef[] { return getAvailableConnectionMethods(entry).filter( - (method) => (method.purpose ?? "tool") === "tool", + (method) => (method.purpose ?? "tool") !== "channel", ); } @@ -495,6 +500,9 @@ export function readConnectionIntentOAuthOutcome( } export interface ConnectionSetupFlowProps { + aiConnection?: import("@paperclipai/shared").AiConnectionBinding; + /** Provider-specific authentication inside the existing access/setup shell. Undefined retains the standard credential form. */ + renderCredentialStep?: (context: { app: AppDefinition; name: string; grantKind: ConnectionGrantKind; agentIds: string[]; allAgents: boolean; onBack: () => void }) => ReactNode; byoOnly?: boolean; credentialSource?: ToolConnectionCredentialSource; host?: "page" | "dialog"; @@ -530,8 +538,10 @@ export function ConnectionSetupFlow({ onUseExisting, onComplete, onOAuthDeclined, + aiConnection, onPhaseChange, onCancel, + renderCredentialStep, }: ConnectionSetupFlowProps = {}) { const routeNavigate = useNavigate(); const navigate = useCallback((to: string, options?: { replace?: boolean }) => { @@ -552,6 +562,7 @@ export function ConnectionSetupFlow({ const sourceSlug = searchParams.get("source")?.trim() || null; const createNewConnection = forceNewConnection || searchParams.get("new") === "1"; const routeStage = searchParams.get("stage")?.trim() || null; + const requestedMethodKey = searchParams.get("method")?.trim() || null; const resumeConnectionId = searchParams.get("resume")?.trim() || null; const oauthCallbackOutcome = searchParams.get("oauth"); const oauthCallbackCode = searchParams.get("code"); @@ -621,7 +632,7 @@ export function ConnectionSetupFlow({ const [curatedOAuthClientId, setCuratedOAuthClientId] = useState(""); const [curatedOAuthClientSecret, setCuratedOAuthClientSecret] = useState(""); const [vercelConnector, setVercelConnector] = useState(""); - const [connectionMethodKey, setConnectionMethodKey] = useState(""); + const [connectionMethodKey, setConnectionMethodKey] = useState(aiConnection ? `ai-${aiConnection.method}` : ""); const [configValues, setConfigValues] = useState>({}); const [googleSheetsLinks, setGoogleSheetsLinks] = useState(""); const [googleSheetsError, setGoogleSheetsError] = useState(null); @@ -906,6 +917,10 @@ export function ConnectionSetupFlow({ const galleryQuery = useQuery({ queryKey: queryKeys.apps.gallery(selectedCompanyId ?? "__none__"), queryFn: () => toolsApi.listGallery(selectedCompanyId!), + select: useCallback((data: Awaited>) => connectionIntentId ? { + ...data, + apps: data.apps.map(app => ({ ...app, methods: app.methods.filter(method => aiConnection ? method.ai?.provider === aiConnection.provider && method.ai.method === aiConnection.method : method.transport !== "runtime_auth") })).filter(app => app.methods.length > 0), + } : data, [connectionIntentId, aiConnection?.provider, aiConnection?.method]), enabled: !!selectedCompanyId, }); // Use the same visible catalog for cards and every branded URL shortcut. @@ -1136,6 +1151,7 @@ export function ConnectionSetupFlow({ setStep(nextStep); if (entry) { navigate(appConnectHref(entry.slug, nextStep, credentialSource, { + connectionMethodKey: entry.methods.find(method => method.key === connectionMethodKey)?.ai ? connectionMethodKey : undefined, resumeConnectionId, reconnectConnectionId, interactionId: connectionIntentId, @@ -1402,7 +1418,13 @@ export function ConnectionSetupFlow({ && connectorEnrollmentQuery.isLoading ) return; const methods = connectionMethodsForCredentialSource(requestedEntry, credentialSource); - const initialMethod = ( + const requestedAi = aiConnection ?? (reconnectConnection?.connectionPurpose === "ai" + ? aiConnectionMetadataSchema.safeParse(reconnectConnection.config?.ai).data + : undefined); + const explicitMethod = methods.find(candidate => requestedAi + ? candidate.ai?.provider === requestedAi.provider && candidate.ai.method === requestedAi.method + : candidate.key === requestedMethodKey); + const initialMethod = explicitMethod ?? ( requestedDefinitionUsesManagedConnector && !requestedEntryAdvertisesManagedConnector ? recommendedManagedConnectorMethod(fullRequestedDefinition) @@ -1502,6 +1524,8 @@ export function ConnectionSetupFlow({ return; } }, [ + aiConnection, + requestedMethodKey, applicationsQuery.isError, applicationsQuery.isFetchedAfterMount, applicationsQuery.data, @@ -1529,6 +1553,10 @@ export function ConnectionSetupFlow({ zapierSource, ]); + useEffect(() => { + if (reconnectConnection?.connectionPurpose === "ai" && step === "access") setStep("key"); + }, [reconnectConnection?.connectionPurpose, step]); + // Resume the exact method and non-secret provider configuration that the // interrupted draft already chose. Secrets are intentionally never read back // into the browser; credential-based methods ask for a replacement value. @@ -1806,38 +1834,22 @@ export function ConnectionSetupFlow({ Reuse a connection without changing who already has access, or connect a new one.

-
- {existingConnections.map((connection) => ( - - ))} -
+ ({ + id: connection.id, name: connection.name, + description: connection.status === "active" && connection.enabled ? "Ready to use" : "Setup needs attention", + }))} + pendingId={existingConnectionPendingId} + onSelect={async (id) => { + setExistingConnectionPendingId(id); + setExistingConnectionError(null); + try { await onUseExisting(id); } + catch (error) { + setExistingConnectionError(error instanceof Error ? error.message : "Couldn’t use this connection."); + setExistingConnectionPendingId(null); + } + }} + /> {existingConnectionError ? ( {existingConnectionError} ) : null} @@ -2023,7 +2035,23 @@ export function ConnectionSetupFlow({ const zapierEntry = zapierSource ? galleryQuery.data?.apps.find((app) => app.slug === "zapier") ?? null : null; - const stepLabels = zapierSource + const reconnectAiMethod = reconnectConnection?.connectionPurpose === "ai" + ? aiConnectionMetadataSchema.safeParse(reconnectConnection.config?.ai).data + : undefined; + const aiMethod = reconnectAiMethod ?? entry?.methods.find(method => method.key === connectionMethodKey)?.ai + ?? (!connectionMethodKey && entry?.methods.every(method => method.ai) ? entry.methods[0]?.ai : undefined); + const credentialStep = entry ? renderCredentialStep?.({ app: entry, name: galleryName || entry.name, grantKind: effectiveGrantKind, agentIds: [...installAgentIds], allAgents: installChoice === "all", onBack: () => setAppStep("access") }) ?? (aiMethod && selectedCompanyId ? <> onCancel ? onCancel() : navigate("/apps")} + onComplete={result => { onComplete?.({ connectionId: result.connectionId }); if (!onComplete) navigate(`/apps/${result.connectionId}/permissions`); }} + /> : undefined) : undefined; + const stepLabels = reconnectConnection?.connectionPurpose === "ai" ? ["Reconnect account"] : credentialStep !== undefined + ? ["Access", "Connect account"] + : zapierSource ? ZAPIER_STEP_LABELS : entry && setupCredentialSourceMethods.length > 1 ? ["Access", "Choose connection"] @@ -2055,7 +2083,7 @@ export function ConnectionSetupFlow({ ? `Continue to ${entry?.name ?? "sign-in"}` : accessStepAuthKind === "oauth" ? "Continue" : "Save and continue"; - const stepIndex = (zapierSource || entry) && step !== "gallery" && step !== "success" + const stepIndex = reconnectConnection?.connectionPurpose === "ai" ? 0 : (zapierSource || entry) && step !== "gallery" && step !== "success" ? SELECTED_APP_STEP_INDEX[step] : step === "success" ? stepLabels.length @@ -2188,7 +2216,7 @@ export function ConnectionSetupFlow({ - ) : step === "key" && entry ? ( + ) : step === "key" && entry && credentialStep !== undefined ? credentialStep : step === "key" && entry ? ( { if (directOAuthEntry) { diff --git a/ui/src/index.css b/ui/src/index.css index bd6796564b..83037058f5 100644 --- a/ui/src/index.css +++ b/ui/src/index.css @@ -2999,3 +2999,23 @@ span.paperclip-mention-chip[data-mention-kind="external-object"] { .runner-activity-roll-in { animation: none; } .runner-activity-roll-out { display: none; } } + +/* Keep entity-picker search controls visible when a narrow viewport opens its + software keyboard. The fixed sheet also avoids scale motion during mobile + viewport resizing. */ +@media (max-width: 40rem) { + [data-mobile-entity-picker] { + position: fixed !important; + inset: auto calc(var(--spacing) * 4) max(calc(var(--spacing) * 4), env(safe-area-inset-bottom)) !important; + width: auto !important; + max-width: none !important; + max-height: calc(100dvh - calc(var(--spacing) * 8)) !important; + transform: none !important; + transform-origin: bottom center !important; + animation: none !important; + } + + [data-mobile-entity-picker] [data-slot="command-list"] { + max-height: min(var(--sz-300px), calc(100dvh - calc(var(--spacing) * 24))) !important; + } +} diff --git a/ui/src/lib/codemirror-single-instance.test.ts b/ui/src/lib/codemirror-single-instance.test.ts new file mode 100644 index 0000000000..570a6fc7e8 --- /dev/null +++ b/ui/src/lib/codemirror-single-instance.test.ts @@ -0,0 +1,129 @@ +import { existsSync, readdirSync, readFileSync, realpathSync } from "node:fs"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; + +// CodeMirror validates extensions with instanceof, so the installed graph +// must contain exactly one physical copy of these packages. Two resolved +// versions ship two module instances and crash the editor at runtime with +// "Unrecognized extension value in extension set" — a failure no unit test +// of the editor itself catches, because each test file sees only one copy. +// The pnpm.overrides entries in the root package.json hold the graph to a +// single resolution; this file pins that invariant against the graph the +// current install actually resolved, so it holds wherever the tests run — +// CI (which installs from the lockfile it regenerates for the PR) and +// local checkouts alike. +const SINGLE_INSTANCE_PACKAGES = ["@codemirror/state", "@codemirror/view"]; + +const repoRoot = path.resolve(__dirname, "../../.."); +const uiRoot = path.resolve(__dirname, "../.."); +const workspaceManifest = readFileSync( + path.join(repoRoot, "pnpm-workspace.yaml"), + "utf8", +); +const rootManifest = JSON.parse( + readFileSync(path.join(repoRoot, "package.json"), "utf8"), +) as { pnpm?: { overrides?: Record } }; + +/** + * Every physical copy of `target` reachable from the ui package's + * installed graph. Walks the graph the way pnpm lays it out — each + * package's dependencies are linked either in its own node_modules + * (workspace packages) or beside it in its .pnpm bucket — and realpaths + * every link, so each resolved version collapses to one path. Walking + * from the ui roots keeps orphaned .pnpm buckets from older installs out + * of the census. + */ +function reachableCopies(target: string): string[] { + const copies = new Set(); + const visited = new Set(); + const queue: string[] = [uiRoot]; + + const packageEntries = (nodeModulesDir: string): string[] => { + const entries: string[] = []; + for (const name of readdirSync(nodeModulesDir)) { + if (name.startsWith(".")) continue; + if (name.startsWith("@")) { + const scopeDir = path.join(nodeModulesDir, name); + for (const child of readdirSync(scopeDir)) { + if (!child.startsWith(".")) entries.push(`${name}/${child}`); + } + } else { + entries.push(name); + } + } + return entries; + }; + + while (queue.length > 0) { + const packageDir = queue.shift()!; + if (visited.has(packageDir)) continue; + visited.add(packageDir); + if (visited.size > 20_000) { + throw new Error("dependency walk exceeded its safety bound"); + } + + // A package's dependencies live in its own node_modules (workspace + // packages) and, for store-installed packages, beside it in the + // .pnpm bucket's shared node_modules. + const dependencyDirs = [path.join(packageDir, "node_modules")]; + const parent = path.dirname(packageDir); + const grandparent = path.dirname(parent); + if (path.basename(parent) === "node_modules") { + dependencyDirs.push(parent); + } else if (path.basename(grandparent) === "node_modules") { + dependencyDirs.push(grandparent); // scoped package + } + + for (const dependencyDir of dependencyDirs) { + if (!existsSync(dependencyDir)) continue; + for (const entry of packageEntries(dependencyDir)) { + let entryDir: string; + try { + entryDir = realpathSync(path.join(dependencyDir, entry)); + } catch { + continue; // dangling symlink + } + if (entry === target) copies.add(entryDir); + queue.push(entryDir); + } + } + } + return [...copies]; +} + +describe("codemirror single-instance invariant", () => { + for (const pkg of SINGLE_INSTANCE_PACKAGES) { + it(`keeps the ${pkg} override in the root manifest and its workspace mirror`, () => { + // Removing the override is the only way a second copy can come + // back (an override rewrites every dependent's range), so the + // override's presence is the other half of the invariant. + expect( + rootManifest.pnpm?.overrides?.[pkg], + `${pkg} must stay in pnpm.overrides (root package.json); without ` + + "it the graph can resolve two copies and instanceof checks " + + "inside the editor break.", + ).toMatch(/^\^6\./); + expect( + workspaceManifest, + `pnpm-workspace.yaml mirrors the pnpm.overrides block and must ` + + `carry the same ${pkg} entry.`, + ).toMatch(new RegExp(`^\\s+"${pkg}":`, "m")); + }); + + it(`installs exactly one physical copy of ${pkg}`, () => { + const copies = reachableCopies(pkg); + expect( + copies.length, + `${pkg} is not installed anywhere in the ui graph`, + ).toBeGreaterThan(0); + expect( + copies, + `the installed graph carries multiple physical copies of ${pkg}, ` + + "which break instanceof checks inside the editor. Reinstall " + + "against the current manifests; if the copies persist, fix the " + + "pnpm.overrides entry in the root package.json instead of " + + "allowing a second copy.", + ).toHaveLength(1); + }); + } +}); diff --git a/ui/src/lib/issue-attachments.ts b/ui/src/lib/issue-attachments.ts index 3f2b4167a5..5c7a73b2e8 100644 --- a/ui/src/lib/issue-attachments.ts +++ b/ui/src/lib/issue-attachments.ts @@ -25,7 +25,8 @@ export function attachmentDownloadPath(attachment: AttachmentPathLike) { } export function isImageAttachment(attachment: Pick) { - return normalizedContentType(attachment).startsWith("image/"); + const type = normalizedContentType(attachment); + return type.startsWith("image/") && !/^image\/hei[cf](?:-sequence)?$/.test(type); } export function isVideoAttachment( diff --git a/ui/src/lib/issue-chat-messages.test.ts b/ui/src/lib/issue-chat-messages.test.ts index 3d325d7e3f..20f9041677 100644 --- a/ui/src/lib/issue-chat-messages.test.ts +++ b/ui/src/lib/issue-chat-messages.test.ts @@ -4,6 +4,7 @@ import { buildAssistantPartsFromTranscript, buildIssueChatMessages, isCoTSegmentActive, + isRedundantAiRecoveryNotice, preserveReadableStreamingRetraction, stabilizeThreadMessages, type IssueChatComment, @@ -1792,3 +1793,18 @@ describe("stabilizeThreadMessages", () => { expect(secondStable.messages).toBe(firstStable.messages); }); }); + + +describe("AI recovery presentation", () => { + it.each(["pending", "accepted", "rejected", "expired"] as const)("replaces diagnostic notices with the same-run %s connection card", (status) => { + const interaction: ConnectionIntentInteraction = { ...pendingConnectionIntentInteraction, status, sourceRunId: "failed-run", payload: { ...pendingConnectionIntentInteraction.payload, purpose: "ai" } }; + const notice = createComment({ authorType: "system", presentation: { kind: "system_notice", title: "AI connection needs attention", tone: "danger", detailsDefaultOpen: false }, metadata: { version: 1, sourceRunId: "failed-run", sections: [] } }); + expect(isRedundantAiRecoveryNotice(notice, [interaction])).toBe(true); + expect(isRedundantAiRecoveryNotice(notice, [{ ...interaction, sourceRunId: "other-run" }])).toBe(false); + expect(isRedundantAiRecoveryNotice(notice, [])).toBe(false); + expect(isRedundantAiRecoveryNotice(notice, [{ ...interaction, payload: { ...interaction.payload, purpose: undefined } }])).toBe(false); + const messages = buildIssueChatMessages({ comments: [notice], interactions: [interaction], timelineEvents: [], linkedRuns: [], liveRuns: [] }); + expect(messages).toHaveLength(1); + expect(messages[0]?.metadata.custom).toMatchObject({ kind: "interaction" }); + }); +}); diff --git a/ui/src/lib/issue-chat-messages.ts b/ui/src/lib/issue-chat-messages.ts index b2048c38b9..bc6e7dfef1 100644 --- a/ui/src/lib/issue-chat-messages.ts +++ b/ui/src/lib/issue-chat-messages.ts @@ -1141,6 +1141,20 @@ function createLiveRunMessage(args: { return message; } +/** The durable AI interaction owns repair and its receipt; don't also show the + * escalation's diagnostic card for that same failure. Keep unmatched notices. */ +export function isRedundantAiRecoveryNotice( + comment: IssueChatComment, + interactions: readonly IssueThreadInteraction[] = [], +): boolean { + return comment.presentation?.kind === "system_notice" + && ["AI connection needs attention", "Configuration incomplete"].includes(comment.presentation.title ?? "") + && Boolean(comment.metadata?.sourceRunId) + && interactions.some((interaction) => interaction.kind === "connection_intent" + && interaction.payload.purpose === "ai" + && interaction.sourceRunId === comment.metadata?.sourceRunId); +} + export function buildIssueChatMessages(args: { comments: readonly IssueChatComment[]; interactions?: readonly IssueThreadInteraction[]; @@ -1181,6 +1195,7 @@ export function buildIssueChatMessages(args: { const orderedMessages: MessageWithOrder[] = []; for (const comment of sortByCreated(comments)) { + if (isRedundantAiRecoveryNotice(comment, interactions)) continue; orderedMessages.push({ createdAtMs: toTimestamp(comment.createdAt), order: 1, diff --git a/ui/src/lib/issue-queued-comment-queue.test.ts b/ui/src/lib/issue-queued-comment-queue.test.ts index dae5ee5982..6e703eea17 100644 --- a/ui/src/lib/issue-queued-comment-queue.test.ts +++ b/ui/src/lib/issue-queued-comment-queue.test.ts @@ -32,6 +32,7 @@ describe("normalizeIssueQueuedCommentQueue", () => { revision: "rev-1", protocol: "paperclip_runner_v1", steeringDisposition: "available", + executionWait: { reason: "remote_cleanup", message: "Waiting for the previous environment to stop." }, entries: [ { comment: { id: "second", body: "Second" }, @@ -66,6 +67,7 @@ describe("normalizeIssueQueuedCommentQueue", () => { expect(queue.queueId).toBe("wake-1"); expect(queue.state).toBe("deferred"); expect(queue.steeringDisposition).toBe("available"); + expect(queue.executionWait?.reason).toBe("remote_cleanup"); }); it("fails closed for malformed protocol and steering data", () => { @@ -122,6 +124,7 @@ describe("normalizeIssueQueuedCommentQueue", () => { revision: "rev-1", protocol: "paperclip_runner_v1", steeringDisposition: "available", + executionWait: { reason: "remote_cleanup", message: "Waiting for the previous environment to stop." }, entries: [ { comment: pending, @@ -143,6 +146,7 @@ describe("normalizeIssueQueuedCommentQueue", () => { expect(queue?.queueId).toBe("wake-1"); expect(queue?.steeringDisposition).toBe("available"); + expect(queue?.executionWait?.reason).toBe("remote_cleanup"); expect(queue?.entries.map((entry) => entry.comment.id)).toEqual([ "comment-1", ]); diff --git a/ui/src/lib/issue-queued-comment-queue.ts b/ui/src/lib/issue-queued-comment-queue.ts index 52a03547e0..28dbbd2be8 100644 --- a/ui/src/lib/issue-queued-comment-queue.ts +++ b/ui/src/lib/issue-queued-comment-queue.ts @@ -54,6 +54,7 @@ export function normalizeIssueQueuedCommentQueue( .map((entry, position) => ({ ...entry, position })); const disposition = source?.steeringDisposition; const state = source?.state; + const wait = record(source?.executionWait); return { issueId: @@ -80,6 +81,9 @@ export function normalizeIssueQueuedCommentQueue( ? (disposition as IssueQueuedCommentSteeringDisposition) : "unsupported", entries, + executionWait: typeof wait?.reason === "string" && typeof wait?.message === "string" + ? { reason: wait.reason, message: wait.message } + : null, }; } @@ -145,5 +149,6 @@ export function mergePendingIssueQueuedComments(params: { ? "temporarily_unavailable" : "unsupported"), entries, + executionWait: params.authoritativeQueue?.executionWait ?? null, }; } diff --git a/ui/src/lib/saved-provider-credentials.ts b/ui/src/lib/saved-provider-credentials.ts index 35abf6ee55..1433d5f670 100644 --- a/ui/src/lib/saved-provider-credentials.ts +++ b/ui/src/lib/saved-provider-credentials.ts @@ -1,10 +1,25 @@ -import type { CompanySecret, EnvBinding } from "@paperclipai/shared"; +import type { AiConnectionBinding, AiManagedConnectionSummary, AiProvider, CompanySecret, EnvBinding } from "@paperclipai/shared"; import type { MyUserSecretEntry } from "../api/secrets"; -export interface SavedProviderKey { - id: string; - label: string; - binding: EnvBinding; +export type SavedProviderKey = { id: string; label: string } & ( + | { binding: EnvBinding; aiConnection?: never } + | { binding?: never; aiConnection: AiConnectionBinding } +); + +export function savedManagedProviderAccounts( + companyId: string, provider: AiProvider, currentUserId: string, + connections: AiManagedConnectionSummary[], +): SavedProviderKey[] { + return connections.flatMap((account) => { + if (account.companyId !== companyId || account.provider !== provider || account.status !== "connected") return []; + if (account.ownership === "personal" && account.ownerUserId === currentUserId && account.isDefault) { + return [{ id: `ai:${account.grantId}`, label: `${account.name} (Your default)`, aiConnection: { provider, method: account.method, mode: "responsible_user" as const } }]; + } + if (account.ownership === "shared") { + return [{ id: `ai:${account.grantId}`, label: `${account.name} (Company shared)`, aiConnection: { provider, method: account.method, mode: "shared" as const, connectionId: account.id, grantId: account.grantId } }]; + } + return []; + }); } /** Match the canonical onboarding key and distinct keys created by agent setup. */ diff --git a/ui/src/lib/test-agent-setup.ts b/ui/src/lib/test-agent-setup.ts index 8d0f2dd2e2..ab77d57f77 100644 --- a/ui/src/lib/test-agent-setup.ts +++ b/ui/src/lib/test-agent-setup.ts @@ -6,16 +6,18 @@ import { agentsApi } from "../api/agents"; * the adapter's existing read-only CLI hello probe before calling setup connected. */ export async function testAgentSetup(input: { companyId: string; + agentId?: string; adapterType: string; providerAdapter: string; adapterConfig: Record; - agentId?: string; + aiConnection?: import("@paperclipai/shared").AiConnectionBinding; testCredentials?: Record; environmentId: string | null; }): Promise { const payload = { - adapterConfig: input.adapterConfig, ...(input.agentId ? { agentId: input.agentId } : {}), + ...(input.aiConnection ? { aiConnection: input.aiConnection } : {}), + adapterConfig: input.adapterConfig, ...(input.testCredentials ? { testCredentials: input.testCredentials } : {}), environmentId: input.environmentId, }; diff --git a/ui/src/pages/DesignGuide.tsx b/ui/src/pages/DesignGuide.tsx index 3b42b7a80b..66d80edce4 100644 --- a/ui/src/pages/DesignGuide.tsx +++ b/ui/src/pages/DesignGuide.tsx @@ -1,5 +1,6 @@ import { TaskChatProjectCreatedCard } from "@/components/task-chat/TaskChatProjectCreatedCard"; import { TaskDetailTasksPanel } from "@/components/task-detail/TaskDetailTasksPanel"; +import { AiConnectionDesignExamples } from "@/components/ai-connections/AiConnectionDesignExamples"; import { SavedProviderKeySelect } from "../components/onboarding/SavedProviderKeySelect"; import { RepositoryEditor } from "@/components/RepositoryEditor"; import { TaskChatRunnerActivityGroup } from "@/components/task-chat/TaskChatRunnerActivityGroup"; @@ -2352,6 +2353,10 @@ export function DesignGuide() { +
+ +
+

A derived lifecycle chip (amber) for attention states. The lifecycle chip is separate from diff --git a/ui/src/pages/IssueDetail.test.tsx b/ui/src/pages/IssueDetail.test.tsx index cf08b15694..489da7c607 100644 --- a/ui/src/pages/IssueDetail.test.tsx +++ b/ui/src/pages/IssueDetail.test.tsx @@ -162,9 +162,12 @@ class ResizeObserverStub { (globalThis as any).ResizeObserver = (globalThis as any).ResizeObserver ?? ResizeObserverStub; -vi.mock("../api/issues", () => ({ - issuesApi: mockIssuesApi, -})); +vi.mock("../api/issues", async (importOriginal) => { + const actual = await importOriginal(); + // Keep composed API operations real while replacing their request methods. + // This also exercises the current-revision check used by both queue surfaces. + return { ...actual, issuesApi: Object.assign(actual.issuesApi, mockIssuesApi) }; +}); vi.mock("../api/activity", () => ({ activityApi: mockActivityApi, diff --git a/ui/src/pages/IssueDetail.tsx b/ui/src/pages/IssueDetail.tsx index e73980c96e..c66f823c47 100644 --- a/ui/src/pages/IssueDetail.tsx +++ b/ui/src/pages/IssueDetail.tsx @@ -1279,7 +1279,7 @@ type IssueDetailChatTabProps = { onReviewConversation: () => Promise; onImageUpload: (file: File) => Promise; onAttachImage: (file: File) => Promise; - onInterruptQueued: (runId: string) => Promise; + onInterruptQueued: (runId: string | null) => Promise; onDeleteComment?: (commentId: string) => Promise | void; onPauseWorkRun?: (runId: string, feedback?: "composer") => Promise; pauseWorkPending?: boolean; @@ -4952,21 +4952,13 @@ export function TaskDetailSurface({ conversation, tasksTab }: { tasksTab?: TaskS }); const interruptQueuedComment = useMutation({ - mutationFn: async (runId: string) => { - const queue = await issuesApi.getQueuedComments(issueId!); - if (!queue.queueId || queue.targetRunId !== runId) { - throw new Error("The queued messages changed. Refresh and try again."); - } - return issuesApi.interruptQueuedComments(issueId!, { - queueId: queue.queueId, revision: queue.revision, targetRunId: runId, - }); - }, + mutationFn: (runId: string | null) => issuesApi.interruptLatestQueuedComments(issueId!, runId), onSuccess: () => { invalidateIssueDetail(); invalidateIssueRunState(); pushToast({ title: "Interrupt requested", - body: "The active run is stopping so queued comments can continue next.", + body: "Queued messages will be sent when the previous run has stopped.", tone: "success", }); }, @@ -6155,7 +6147,7 @@ export function TaskDetailSurface({ conversation, tasksTab }: { tasksTab?: TaskS [uploadAttachment], ); const handleInterruptQueuedRun = useCallback( - async (runId: string) => { + async (runId: string | null) => { await interruptQueuedComment.mutateAsync(runId); }, [interruptQueuedComment], diff --git a/ui/src/pages/NewAgent.test.tsx b/ui/src/pages/NewAgent.test.tsx index 8f1de89d01..d71f1ef6a7 100644 --- a/ui/src/pages/NewAgent.test.tsx +++ b/ui/src/pages/NewAgent.test.tsx @@ -39,6 +39,11 @@ const state = vi.hoisted(() => ({ navigate: vi.fn(), openNewIssue: vi.fn(), })); +const managedApi = vi.hoisted(() => ({ + list: vi.fn(async () => ({ currentUserId: "user-1", connections: [] })), + create: vi.fn(async () => ({ connectionId: "managed-connection", grantId: "managed-grant" })), +})); +vi.mock("@/api/ai-connections", () => ({ aiConnectionsApi: managedApi })); vi.mock("@/api/agents", () => ({ agentsApi: api })); vi.mock("@/api/environments", () => ({ environmentsApi: envApi })); vi.mock("@/api/instanceSettings", () => ({ instanceSettingsApi: settings })); @@ -287,9 +292,9 @@ describe("New agent setup", () => { expect(api.hire.mock.calls[0][1].adapterConfig.apiKey).toMatchObject({ type: "secret_ref", secretId: "org-secret-1" }); expect(JSON.stringify(api.hire.mock.calls)).not.toContain("hermes-test-key"); }); - it("shows Grok login guidance and hides ignored Kimi and OpenCode effort controls", async () => { + it("uses the shared Grok connection flow and hides ignored Kimi and OpenCode effort controls", async () => { await render("grok_local"); - expect(container.textContent).toContain("grok login"); + expect(container.textContent).toContain("Connect Atlas to Grok"); await render("opencode_local"); expect(container.querySelector('[aria-label="Thinking effort"]')).toBeNull(); }); @@ -374,16 +379,20 @@ describe("New agent setup", () => { ["codex_local", "codex", "OpenAI", "OPENAI_API_KEY"], ["paperclip_runner", "claude", "Claude", "ANTHROPIC_API_KEY"], ["paperclip_runner", "codex", "OpenAI", "OPENAI_API_KEY"], - ])("stores %s %s API credentials only when finishing", async (adapter, runner, provider, key) => { + ])("stores %s %s as a reusable connection before hiring", async (adapter, runner, provider, key) => { await render(adapter, runner); await click("Use API key insteadUse subscription insteadUse API key instead"); await click(provider + "API"); await fill("API key", "connection-key"); await click("Connect"); - expect(api.testEnvironment.mock.calls[0][2].testCredentials).toEqual({ [key]: "connection-key" }); + const binding = { provider: key === "ANTHROPIC_API_KEY" ? "anthropic" : "openai", method: "api_key", mode: "responsible_user" }; + expect(managedApi.create).toHaveBeenCalledWith("company-1", expect.objectContaining({ apiKey: "connection-key", provider: binding.provider })); + expect(api.testEnvironment.mock.calls[0][2].testCredentials).toEqual({}); + expect(api.testEnvironment.mock.calls[0][2].aiConnection).toEqual(binding); expect(secrets.createUserSecretDefinition).not.toHaveBeenCalled(); await click("Finish setup"); - expect(api.hire.mock.calls[0][1].adapterConfig.env[key].type).toBe("user_secret_ref"); + expect(api.hire.mock.calls[0][1].runtimeConfig.aiConnection).toEqual(binding); + expect(managedApi.create).toHaveBeenCalledTimes(1); expect(JSON.stringify(api.hire.mock.calls)).not.toContain("connection-key"); }); it.each([ @@ -409,7 +418,7 @@ describe("New agent setup", () => { expect(secrets.createMyUserSecret).not.toHaveBeenCalled(); expect(secrets.rotateMyUserSecret).not.toHaveBeenCalled(); }); - it.each(["opencode_local", "pi_local"])( + it.each(["pi_local"])( "persists %s OpenRouter credentials only as a secret reference", async (adapter) => { await render(adapter); @@ -434,6 +443,46 @@ describe("New agent setup", () => { expect(secrets.create).toHaveBeenCalledTimes(1); }, ); + it("connects OpenRouter before testing and hiring OpenCode without copying credentials into the agent", async () => { + await render("opencode_local"); + const model = "openrouter/anthropic/claude-sonnet-4.6"; + await fill("Model", model); + await click("Connect another account"); + const dialog = document.querySelector('[role="dialog"]')!; + expect(dialog).toBeTruthy(); + expect(api.hire).not.toHaveBeenCalled(); + expect(api.testEnvironment).not.toHaveBeenCalled(); + const input = dialog.querySelector('[aria-label="API key"]') as HTMLInputElement; + expect(input).toBeTruthy(); + await act(async () => { + Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")!.set!.call(input, "example-test-secret"); + input.dispatchEvent(new Event("input", { bubbles: true })); + }); + const connectButton = [...dialog.querySelectorAll("button")].find((button) => button.textContent?.trim() === "Connect")!; + expect(connectButton.disabled).toBe(false); + await act(async () => connectButton.click()); + await settle(); + expect(document.querySelector('[role="dialog"]')).toBeNull(); + expect(managedApi.create).toHaveBeenCalledWith("company-1", expect.objectContaining({ + provider: "openrouter", method: "api_key", apiKey: "example-test-secret", + })); + const binding = { provider: "openrouter", method: "api_key", mode: "responsible_user" }; + await click("Run test"); + expect(api.testEnvironment.mock.calls[0][2]).toEqual(expect.objectContaining({ + aiConnection: binding, testCredentials: {}, + adapterConfig: expect.objectContaining({ model }), + })); + await click("Finish setup"); + expect(api.hire.mock.calls[0][1]).toEqual(expect.objectContaining({ + adapterType: "opencode_local", + runtimeConfig: expect.objectContaining({ aiConnection: binding }), + adapterConfig: expect.objectContaining({ model }), + })); + expect(managedApi.create).toHaveBeenCalledTimes(1); + expect(secrets.create).not.toHaveBeenCalled(); + expect(JSON.stringify(api.testEnvironment.mock.calls)).not.toContain("example-test-secret"); + expect(JSON.stringify(api.hire.mock.calls)).not.toContain("example-test-secret"); + }); it.each(["codex", "claude", "opencode"])( "uses the correct native %s runner", async (runner) => { diff --git a/ui/src/pages/Pipelines.tsx b/ui/src/pages/Pipelines.tsx index ba737e8ba0..926ea7f386 100644 --- a/ui/src/pages/Pipelines.tsx +++ b/ui/src/pages/Pipelines.tsx @@ -2409,10 +2409,17 @@ export function PipelineItemDetailView({ pipelineId, caseId }: { pipelineId: str await queryClient.invalidateQueries({ queryKey: queryKeys.issues.comments(conversationIssueId) }); }, [conversationIssueId, queryClient]); - const handleInterruptConversationQueuedRun = useCallback(async (runId: string) => { - await heartbeatsApi.cancel(runId); - await invalidateConversation(); - }, [invalidateConversation]); + const handleInterruptConversationQueuedRun = useCallback(async (runId: string | null) => { + if (!conversationIssueId) return; + try { + await issuesApi.interruptLatestQueuedComments(conversationIssueId, runId); + pushToast({ title: "Interrupt requested", body: "Queued messages will be sent when the previous run has stopped.", tone: "success" }); + } catch (error) { + pushToast({ title: "Interrupt failed", body: error instanceof Error ? error.message : "Unable to send queued messages", tone: "error" }); + } finally { + await invalidateConversation(); + } + }, [conversationIssueId, invalidateConversation, pushToast]); const handleCancelConversationQueuedComment = useCallback(async (commentId: string) => { if (!conversationIssueId) return; diff --git a/ui/src/pages/Search.test.tsx b/ui/src/pages/Search.test.tsx index 2618e93322..1fa6715be6 100644 --- a/ui/src/pages/Search.test.tsx +++ b/ui/src/pages/Search.test.tsx @@ -263,6 +263,30 @@ describe("Search page", () => { }); }); + it.each(["relevance", "updated"])("preserves server %s order across result sources", async (sort) => { + const results = ["document", "title", "comment"].map((field, index) => ({ + id: `rank-${index}`, type: "issue", score: 300 - index, + title: `Rank ${index}`, href: `/PAP/issues/rank-${index}`, + matchedFields: [field], sourceLabel: field, snippet: field, + snippets: [{ field, label: field, text: field, highlights: [] }], + updatedAt: "2026-01-01T00:00:00.000Z", + issue: { id: `rank-${index}`, identifier: `RANK-${index}`, title: `Rank ${index}`, + status: "todo", priority: "medium", assigneeAgentId: null, assigneeUserId: null, + projectId: null, updatedAt: "2026-01-01T00:00:00.000Z" }, + })); + searchApiMock.search.mockResolvedValue({ query: "rank", normalizedQuery: "rank", scope: "all", + sort, limit: 20, offset: 0, hasMore: false, zeroResults: null, results, + countsByType: { issue: 1, comment: 1, document: 1, artifact: 0, agent: 0, project: 0 }, + filterOptionCounts: { status: {}, priority: {}, assigneeAgentId: {}, assigneeUserId: {}, projectId: {}, labelId: {}, updatedWithin: {} }, + }); + const { root } = renderSearch(`/search?q=rank&sort=${sort}`, container); + await waitForAssertion(() => { + const links = Array.from(container.querySelectorAll('[data-testid="search-results"] a[data-result-type]')); + expect(links.map((link) => link.getAttribute("href"))).toEqual(results.map((result) => result.href)); + }); + flushSync(() => root.unmount()); + }); + it("renders artifact search results in the company search surface", async () => { searchApiMock.search.mockResolvedValueOnce({ query: "launch brief", diff --git a/ui/src/pages/Search.tsx b/ui/src/pages/Search.tsx index 3f528a50f9..b4cc5a4c59 100644 --- a/ui/src/pages/Search.tsx +++ b/ui/src/pages/Search.tsx @@ -15,7 +15,6 @@ import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { Skeleton } from "@/components/ui/skeleton"; import { Badge } from "@/components/ui/badge"; -import { cn } from "@/lib/utils"; import { useNavigate, useSearchParams } from "@/lib/router"; import { useCompany } from "../context/CompanyContext"; import { useBreadcrumbs } from "../context/BreadcrumbContext"; @@ -39,7 +38,6 @@ import { type ParsedSearchQuery, type SearchQueryParserContext, } from "../lib/search-query-parser"; -import { IssueGroupHeader } from "../components/IssueGroupHeader"; import { SearchResultRow } from "../components/search/SearchResultRow"; import { SearchFilterBar, type SearchFilterDataProps } from "../components/search/SearchFilterBar"; import { SearchFilterChips } from "../components/search/SearchFilterChips"; @@ -69,44 +67,6 @@ const SCOPE_LABELS: Record = { projects: "Projects", }; -type SubGroupKey = "issues" | "comments" | "documents" | "artifacts" | "agents" | "projects"; - -const SUBGROUP_ORDER: SubGroupKey[] = ["issues", "comments", "documents", "artifacts", "agents", "projects"]; - -const SUBGROUP_LABELS: Record = { - issues: "Tasks", - comments: "Comments", - documents: "Documents", - artifacts: "Artifacts", - agents: "Agents", - projects: "Projects", -}; - -function classifyResult(result: CompanySearchResult): SubGroupKey { - if (result.type === "artifact") return "artifacts"; - if (result.type === "agent") return "agents"; - if (result.type === "project") return "projects"; - const matched = new Set(result.matchedFields); - if (matched.has("title") || matched.has("identifier") || matched.has("description")) return "issues"; - if (matched.has("comment")) return "comments"; - if (matched.has("document")) return "documents"; - return "issues"; -} - -function buildSubgroups(results: CompanySearchResult[]): Array<{ key: SubGroupKey; results: CompanySearchResult[] }> { - const buckets = new Map(); - for (const result of results) { - const key = classifyResult(result); - const list = buckets.get(key) ?? []; - list.push(result); - buckets.set(key, list); - } - return SUBGROUP_ORDER.filter((key) => (buckets.get(key)?.length ?? 0) > 0).map((key) => ({ - key, - results: buckets.get(key) ?? [], - })); -} - function isCompanySearchScope(value: string | null): value is CompanySearchScope { return Boolean(value) && (COMPANY_SEARCH_SCOPES as readonly string[]).includes(value as string); } @@ -533,8 +493,6 @@ export function Search() { }); }, [counts, data, filtersActive]); - const subgroups = useMemo(() => buildSubgroups(data?.results ?? []), [data?.results]); - const operatorPills = useMemo(() => searchFilterPills(draftFilters, parserContext), [draftFilters, parserContext]); const operatorSuggestions = useMemo( () => (inputFocused ? searchOperatorSuggestions(draftQuery, 4) : []), @@ -721,7 +679,7 @@ export function Search() { refetch={() => void refetch()} recentSearches={recentSearches} onRecentClick={handleRecentClick} - subgroups={subgroups} + results={data?.results ?? []} totalResults={totalResults} allMatchTotal={allMatchTotal} activeFilterCount={activeFilterCount} @@ -767,7 +725,7 @@ interface SearchTabContentProps { refetch: () => void; recentSearches: string[]; onRecentClick: (query: string) => void; - subgroups: Array<{ key: SubGroupKey; results: CompanySearchResult[] }>; + results: CompanySearchResult[]; totalResults: number; allMatchTotal: number; activeFilterCount: number; @@ -792,7 +750,7 @@ function SearchTabContent({ refetch, recentSearches, onRecentClick, - subgroups, + results, totalResults, allMatchTotal, activeFilterCount, @@ -950,47 +908,14 @@ function SearchTabContent({ {isFetching ? Updating… : null} -

- {scope === "all" ? ( - subgroups.map((group, groupIndex) => ( -
0 && "mt-6")} - > - - {group.results.length} - - } - className="pt-2 pb-1 text-(length:--text-micro) tracking-wider text-muted-foreground" - /> -
- {group.results.map((result) => ( - - ))} -
-
- )) - ) : ( -
- {subgroups - .flatMap((group) => group.results) - .map((result) => ( - - ))} -
- )} +
+ {results.map((result) => ( + + ))}
); diff --git a/ui/src/pages/apps/AppDetail.test.tsx b/ui/src/pages/apps/AppDetail.test.tsx index 2dc27c6dfd..c44df98cb9 100644 --- a/ui/src/pages/apps/AppDetail.test.tsx +++ b/ui/src/pages/apps/AppDetail.test.tsx @@ -1337,6 +1337,14 @@ describe("AppDetail", () => { .find((button) => button.textContent?.trim() === label); } + it("does not label a revoked AI credential as Connected", async () => { + getConnectionMock.mockResolvedValue(connection({ connectionPurpose: "ai", transport: "runtime_auth", healthStatus: "ok", config: { provider: "openai", method: "api_key" } })); + listConnectionGrantsMock.mockResolvedValue({ connection: { id: "conn-1" }, grants: [organizationGrant({ status: "revoked" })], capabilities: fullCapabilities(), currentUserId: "user-1", members: [] }); + await renderAppDetail(); + expect(container.textContent).toContain("Revoked"); + expect(container.textContent).not.toContain("Connected"); + }); + it("keeps the app header concise on every tab", async () => { mockParams.tab = "permissions"; getConnectionMock.mockResolvedValue(perUserConnection()); diff --git a/ui/src/pages/apps/AppDetail.tsx b/ui/src/pages/apps/AppDetail.tsx index 826fb7693d..45328fbefe 100644 --- a/ui/src/pages/apps/AppDetail.tsx +++ b/ui/src/pages/apps/AppDetail.tsx @@ -1,6 +1,7 @@ +import { ManagedAiConnectionDetails } from "@/components/ai-connections/ManagedAiConnectionDetails"; +import { useEffect, useMemo, useRef, useState, type ReactNode } from "react"; import { EmailConnectionAccess } from "@/components/EmailConnectionAccess"; import { EmailConnectionInboxes } from "./chat/EmailEndpointSetup"; -import { useEffect, useMemo, useRef, useState } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { Check, Loader2, Pencil } from "lucide-react"; import type { @@ -12,6 +13,7 @@ import type { import { connectionDisplaySecondaryHint, humanizeConnectionDisplayName, + aiSubscriptionNeedsIsolatedLogin, isToolConnectionAttentionHealth as isAttentionHealthStatus, } from "@paperclipai/shared"; import { Navigate, useParams, useNavigate, useSearchParams } from "@/lib/router"; @@ -60,7 +62,10 @@ import { export { connectionAddress, connectionTransportLabel }; -export function AppDetail() { +export function AppDetail({ renderActions, onReconnect }: { + renderActions?: (connection: ToolConnection) => ReactNode; + onReconnect?: (connection: ToolConnection) => void; +} = {}) { const { connectionId = "", tab } = useParams<{ connectionId: string; tab?: string }>(); const navigate = useNavigate(); const [searchParams] = useSearchParams(); @@ -235,17 +240,18 @@ export function AppDetail() { () => installStateFrom(installsQuery.data?.installs ?? connection?.installs), [connection?.installs, installsQuery.data?.installs], ); - const access = useMemo(() => accessFrom(profile, install), [profile, install]); + const access = useMemo(() => accessFrom(connection?.connectionPurpose === "ai" ? undefined : profile, install), [connection?.connectionPurpose, profile, install]); const agents = agentsQuery.data ?? []; const [pending, setPending] = useState(false); const persist = useMutation({ - mutationFn: (next: { + mutationFn: async (next: { enabled: Set; askFirst: Set; access: AccessDraft; reviewed?: Set; - }) => - toolsApi.finishApp(selectedCompanyId!, connectionId, { + }) => connection?.connectionPurpose === "ai" + ? toolsApi.putConnectionInstalls(connectionId, next.access.mode === "all" ? [{ targetType: "company", targetId: selectedCompanyId! }] : [...next.access.agentIds].map(targetId => ({ targetType: "agent" as const, targetId }))) + : toolsApi.finishApp(selectedCompanyId!, connectionId, { enabledCatalogEntryIds: [...next.enabled], askFirstCatalogEntryIds: [...next.askFirst].filter((id) => next.enabled.has(id)), ...(next.reviewed ? { reviewedCatalogEntryIds: [...next.reviewed] } : {}), @@ -253,6 +259,7 @@ export function AppDetail() { }), onMutate: () => setPending(true), onSuccess: () => { + void installsQuery.refetch(); queryClient.invalidateQueries({ queryKey: queryKeys.tools.testAgentAccessesForConnection(connectionId) }); queryClient.invalidateQueries({ queryKey: queryKeys.tools.connection(connectionId) }); queryClient.invalidateQueries({ queryKey: queryKeys.tools.catalog(connectionId) }); @@ -477,14 +484,17 @@ export function AppDetail() { ); } - const status = statusFor(connection); + const aiGrantRevoked = connection.connectionPurpose === "ai" + && grantRows.length > 0 && grantRows.every((grant) => grant.status === "revoked"); + const status: StatusInfo = aiGrantRevoked ? { label: "Revoked", tone: "attention" } : statusFor(connection); const needsReconnect = connection.requiresReauthorization ?? (status.tone === "attention" && connection.healthStatus !== "unknown"); const quarantined = catalog.filter((e) => e.status === "quarantined"); const active = catalog.filter((e) => e.status === "active"); const readOnly = active.filter((e) => e.isReadOnly); const canChange = active.filter((e) => !e.isReadOnly); - const actionCount = catalogQuery.data ? active.length : null; + const actionsContent = renderActions?.(connection) ?? (connection.connectionPurpose === "ai" ? : undefined); + const actionCount = actionsContent !== undefined ? null : catalogQuery.data ? active.length : null; const reviewLoading = catalogQuery.isLoading || profilesQuery.isLoading || policiesQuery.isLoading; const permissionsLoading = reviewLoading || installsQuery.isLoading || agentsQuery.isLoading; const reviewFailed = catalogQuery.isError || profilesQuery.isError || policiesQuery.isError; @@ -529,6 +539,7 @@ export function AppDetail() { galleryEntry={logoEntry} canReconnect={canReconnect} reconnectUnavailableMessage={reconnectUnavailableMessage} + onReconnect={onReconnect ? () => onReconnect(connection) : connection.connectionPurpose === "ai" ? () => navigate(`/apps/connect?source=${connection.config?.sourceTemplateKey}&reconnect=${connection.id}`) : undefined} onReconnected={() => { queryClient.invalidateQueries({ queryKey: queryKeys.tools.connection(connectionId) }); queryClient.invalidateQueries({ queryKey: queryKeys.tools.connections(selectedCompanyId) }); @@ -593,8 +604,8 @@ export function AppDetail() { setAudienceOpenGrantId(null); setAudienceError(null); }} - onConnectAsMe={() => startPersonalAuth.mutate()} - onConnectOrganization={() => startOAuth.mutate()} + onConnectAsMe={() => onReconnect ? onReconnect(connection) : startPersonalAuth.mutate()} + onConnectOrganization={() => onReconnect ? onReconnect(connection) : startOAuth.mutate()} onConnectAgent={(agentId) => startOAuth.mutate({ asAgentId: agentId })} onRefreshAccess={() => refreshGitHubAccess.mutate()} refreshAccessPending={refreshGitHubAccess.isPending} @@ -602,12 +613,13 @@ export function AppDetail() { replaceAudience.mutate({ grantId: grant.id, memberUserIds })} /> apply({ access: accessIncludingInstalls(next, install) })} + onSaveAccess={(next) => apply({ access: connection.connectionPurpose === "ai" ? next : accessIncludingInstalls(next, install) })} onRefreshActions={() => refreshTools.mutate()} onSetActionPermission={(id, next) => apply(actionPermissionMutation(id, next, enabledIds, askFirstIds))} onReviewQuarantined={reviewQuarantined} @@ -779,7 +791,7 @@ function statusFor(connection: ToolConnection): StatusInfo { if (connection.enabled === false || connection.status === "disabled") { return { label: "Paused", tone: "paused" }; } - if (isAttentionHealthStatus(connection.healthStatus)) { + if (isAttentionHealthStatus(connection.healthStatus) || (connection.connectionPurpose === "ai" && (connection.healthStatus !== "ok" || aiSubscriptionNeedsIsolatedLogin(connection.config)))) { return { label: "Needs attention", tone: "attention" }; } return { label: "Connected", tone: "connected" }; diff --git a/ui/src/pages/apps/AppsConnect.test.tsx b/ui/src/pages/apps/AppsConnect.test.tsx index f7b3479b44..d096e55a26 100644 --- a/ui/src/pages/apps/AppsConnect.test.tsx +++ b/ui/src/pages/apps/AppsConnect.test.tsx @@ -438,6 +438,27 @@ describe("AppsConnect — Connect with a link (M4 frame)", () => { // credential is entered. // ------------------------------------------------------------------------- + it("keeps the existing Anthropic tool method reachable alongside AI authentication", async () => { + mockParams.appKey = "anthropic"; + listGalleryMock.mockResolvedValue({ apps: [getAppStoreDefinition("anthropic")] }); + await render(); + await passAccessStep(); + expect(container.textContent).toContain("How do you want to connect?"); + expect(radioContaining("Claude subscription")).toBeTruthy(); + expect(radioContaining("Claude API key")).toBeTruthy(); + await act(async () => radioContaining("Use an API key")!.click()); + await flushReact(); + const key = container.querySelector('input[type="password"]'); + expect(key).toBeTruthy(); + await act(async () => setInputValue(key!, "fixture-anthropic-tool-key")); + await act(async () => buttonByText("Connect")!.click()); + await flushReact(); + expect(connectAppMock).toHaveBeenCalledWith("company-1", expect.objectContaining({ + galleryKey: "anthropic", connectionMethodKey: "api-key", + })); + expect(container.textContent).not.toContain("Connect for tool access instead"); + }); + it("asks for a GitHub identity and defaults to the current user and every agent", async () => { mockParams.appKey = "github"; listGalleryMock.mockResolvedValue({ apps: [GITHUB_MANAGED] }); diff --git a/ui/src/pages/apps/Browse.test.tsx b/ui/src/pages/apps/Browse.test.tsx index 2b724709e0..c2d75dfece 100644 --- a/ui/src/pages/apps/Browse.test.tsx +++ b/ui/src/pages/apps/Browse.test.tsx @@ -257,6 +257,7 @@ describe("Connectors landing page", () => { "discord", "github", "gmail", + "imessage-photon", "jira", "microsoft-teams", "notion", diff --git a/ui/src/pages/apps/Browse.tsx b/ui/src/pages/apps/Browse.tsx index 785ea7a7b0..5a956d6bcd 100644 --- a/ui/src/pages/apps/Browse.tsx +++ b/ui/src/pages/apps/Browse.tsx @@ -1,4 +1,5 @@ -import { useEffect, useMemo, useState } from "react"; +import { ManagedAiConnectionRow } from "@/components/ai-connections/ManagedAiConnectionDetails"; +import { useEffect, useMemo, useState, type ReactNode } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { AlertTriangle, @@ -19,6 +20,7 @@ import { getAppDefinitionForUrl, getAppStoreDefinition, isToolConnectionAttentionHealth, + aiSubscriptionNeedsIsolatedLogin, } from "@paperclipai/shared"; import { useNavigate } from "@/lib/router"; import { useChatConnectorsEnabled } from "@/hooks/useChatConnectorsEnabled"; @@ -173,7 +175,7 @@ function connectionState(connection: ToolConnection): ConnectionState { message: "Agents can’t use this account right now.", }; } - if (isToolConnectionAttentionHealth(connection.healthStatus)) { + if ((connection.connectionPurpose === "ai" && (connection.healthStatus !== "ok" || aiSubscriptionNeedsIsolatedLogin(connection.config))) || isToolConnectionAttentionHealth(connection.healthStatus)) { return { kind: "attention", label: "Needs attention", @@ -264,7 +266,7 @@ function accountActionHref( * surface. Connected providers sort first and expand in place to show every * account; unconnected providers retain the same catalog setup flows. */ -export function Browse() { +export function Browse({ renderAccountDetails = (connection) => connection.connectionPurpose === "ai" ? : null }: { renderAccountDetails?: (connection: ToolConnection) => ReactNode } = {}) { const navigate = useNavigate(); const preselectedChatAgentId = typeof window === "undefined" @@ -412,6 +414,7 @@ export function Browse() { }); } const nativeChatProviders = [ + { provider: "imessage-photon", name: "iMessage Photon", description: "Message agents and share photos from Apple Messages with a dedicated Photon number." }, { provider: "slack", name: "Slack", @@ -534,6 +537,7 @@ export function Browse() { discord: "Discord", "microsoft-teams": "Microsoft Teams", telegram: "Telegram", + "imessage-photon": "iMessage Photon", agentmail: "AgentMail", } as const; target = { @@ -674,6 +678,7 @@ export function Browse() {
{visibleRows.map((row) => ( ReactNode; row: ConnectorRowModel; allConnections: ToolConnection[]; userProfileById: ReadonlyMap; @@ -803,6 +810,7 @@ export function ConnectorCard({
{row.connections.map((connection) => ( {accountName} + {details} {state.message ? (
(method.purpose ?? "tool") === "tool" && method.transport !== "chat_sdk", + (method) => (method.purpose ?? "tool") !== "channel" && method.transport !== "chat_sdk", ), }), ); @@ -71,7 +71,7 @@ export function canEnterAppsConnect( if ( !chatConnectorsEnabled && entry?.methods.some((method) => method.transport === "chat_sdk") && - !entry.methods.some((method) => (method.purpose ?? "tool") === "tool" && method.transport !== "chat_sdk") + !entry.methods.some((method) => (method.purpose ?? "tool") !== "channel" && method.transport !== "chat_sdk") ) return false; // A retained connection may belong to a provider hidden from fresh catalog // setup. Admit only known providers here; the setup flow then proves the diff --git a/ui/src/pages/apps/app-detail/AdvancedPanel.tsx b/ui/src/pages/apps/app-detail/AdvancedPanel.tsx index 6679ba9418..ba952948a0 100644 --- a/ui/src/pages/apps/app-detail/AdvancedPanel.tsx +++ b/ui/src/pages/apps/app-detail/AdvancedPanel.tsx @@ -141,12 +141,14 @@ export function ReconnectCard({ connection, galleryEntry, onReconnected, + onReconnect, canReconnect = true, reconnectUnavailableMessage, }: { connection: ToolConnection; galleryEntry: AppDefinition | null; onReconnected: () => void; + onReconnect?: () => void; canReconnect?: boolean; reconnectUnavailableMessage?: string; }) { @@ -216,6 +218,8 @@ export function ReconnectCard({

{reconnectUnavailableMessage ?? "You don't have permission to reconnect this identity."}

+ ) : onReconnect ? ( + ) : managedByVercel && !oauth ? (
); } diff --git a/ui/src/pages/apps/chat/ChatEndpointDetail.tsx b/ui/src/pages/apps/chat/ChatEndpointDetail.tsx index 9056501027..86b3cc7059 100644 --- a/ui/src/pages/apps/chat/ChatEndpointDetail.tsx +++ b/ui/src/pages/apps/chat/ChatEndpointDetail.tsx @@ -54,6 +54,7 @@ const providerNames: Record = { discord: "Discord", "microsoft-teams": "Microsoft Teams", telegram: "Telegram", + "imessage-photon": "iMessage Photon", }; const providerLifecycleGuidance: Record< @@ -85,6 +86,10 @@ const providerLifecycleGuidance: Record< remove: "Paperclip archives the endpoint, stops new ingress, and retires its saved client secret. It does not uninstall the Teams app: the Entra app registration, Azure Bot, custom Teams app, and Teams installations remain until you remove them in Microsoft.", }, + "imessage-photon": { + reconnect: "Reconnect verifies the same Photon project and line allocation, then recovers eligible missed messages.", + remove: "Disconnect archives this channel and removes its saved secret. Your Photon project, number, subscription, and Messages history remain in Photon.", + }, telegram: { reconnect: "Reconnect verifies this same BotFather bot and automatically refreshes its Paperclip webhook and command menu.", @@ -223,6 +228,7 @@ export function ChatEndpointDetail() { : false, }); const endpoint = endpointQuery.data; + const [copyStatus, setCopyStatus] = useState(null); useEffect(() => { if (!endpoint || !activeTab) return; @@ -276,6 +282,16 @@ export function ChatEndpointDetail() {

{endpoint.providerAccountLabel ?? "Chat connection"}

+ {endpoint.provider === "imessage-photon" && endpoint.botExternalId && endpoint.photonAllocation !== "shared" && ( +
+ {endpoint.botExternalId} + + {copyStatus} +
+ )}
{setupIncomplete ? ( @@ -376,6 +392,7 @@ function Settings({ saveResources.mutate({ id: resource.id, enabled }); return (
+ {endpoint.provider === "imessage-photon" &&

{endpoint.photonAllocation === "shared" ? "Shared Photon project · direct messages only. Enroll senders in Photon and link their Messages identities in Access. Groups cannot be enabled." : "Enable each group individually. Agent replies are visible to everyone in that group; only authorized senders can start work."}

} {endpoint.provider === "slack" && endpoint.setup?.command && (

Slack command

@@ -437,11 +454,13 @@ function Settings({ ? (resource.detail ?? resource.type) : "Unavailable at the provider"}

+ {resource.participants?.length ?

Participants: {resource.participants.join(", ")}

: null}
= { discord: "Discord", "microsoft-teams": "Microsoft Teams", telegram: "Telegram", + "imessage-photon": "iMessage Photon", }; const knownProviders = new Set(Object.keys(providerNames)); @@ -128,7 +130,7 @@ export function ChatEndpointSetup() { return params.get("provider") === "agentmail" ? : ; } function ChatSdkEndpointSetup() { - const [params] = useSearchParams(); + const [params, setParams] = useSearchParams(); const navigate = useNavigate(); const queryClient = useQueryClient(); const { selectedCompanyId } = useCompany(); @@ -233,7 +235,14 @@ function ChatSdkEndpointSetup() { provider: provider!, assignedAgentId: agentId, }), - onSuccess: syncEndpointSnapshot, + onSuccess: (next) => { + syncEndpointSnapshot(next); + if (next.provider === "imessage-photon") { + const resumed = new URLSearchParams(params); + resumed.set("resume", next.id); + setParams(resumed, { replace: true }); + } + }, onError: (error) => pushToast({ title: "Couldn't start setup", @@ -248,11 +257,16 @@ function ChatSdkEndpointSetup() { }: { action: ChatEndpointSetupAction; values?: Record; - }) => chatEndpointsApi.setup(endpoint!.id, { action, credentials: values }), + }) => chatEndpointsApi.setup(endpoint!.id, provider === "imessage-photon" ? { + action, + ...(values?.projectSecret ? { credentials: { projectSecret: values.projectSecret } } : {}), + ...(values?.projectId && values.allocation === "shared" ? { photon: { allocation: "shared" as const, projectId: values.projectId } } : values?.projectId && values?.lineId ? { photon: { allocation: "dedicated" as const, projectId: values.projectId, lineId: values.lineId } } : {}), + } : { action, credentials: values }), onMutate: () => setSetupError(null), onSuccess: (next) => { setSetupError(null); syncEndpointSnapshot(next); + setCredentials({}); }, onError: (error, variables) => setSetupError(sanitizedSetupErrorMessage(error, variables.values)), @@ -473,6 +487,7 @@ function ChatSdkEndpointSetup() { agentName={selectedAgent?.name ?? endpoint.assignedAgentName} botLabel={endpoint.botLabel} botUsername={endpoint.botUsername} + photonAllocation={endpoint.photonAllocation} providerUrl={endpoint.setup?.providerUrl} guestIsolationState={ experimentalSettingsQuery.isPending @@ -740,6 +755,7 @@ settings: null, 2, ); + if (provider === "imessage-photon") return ; if (provider === "discord") { const applicationId = credentials.applicationId?.trim() ?? ""; const guildId = credentials.guildId?.trim() ?? ""; @@ -756,7 +772,7 @@ settings: : "Create one dedicated Discord application and bot for this Paperclip agent."}

-
    +
    1. In Discord Developer Portal, create an application. Copy its Application ID from General Information. @@ -1468,6 +1484,7 @@ function TryStep({ agentName, botLabel, botUsername, + photonAllocation, providerUrl, guestIsolationState, pending, @@ -1479,6 +1496,7 @@ function TryStep({ agentName: string; botLabel?: string | null; botUsername?: string | null; + photonAllocation?: "dedicated" | "shared"; providerUrl?: string | null; guestIsolationState: "loading" | "enabled" | "disabled" | "unknown"; pending: boolean; @@ -1490,19 +1508,23 @@ function TryStep({ queryFn: () => chatEndpointsApi.listPrincipals(endpointId), refetchInterval: 1_500, }); + const [numberCopied, setNumberCopied] = useState(false); + const [copyError, setCopyError] = useState(null); const identities = principalsQuery.data ?? []; const unlinkedIdentities = identities.filter( (identity) => identity.status !== "linked", ); const freshConversationInstruction = - provider === "telegram" + provider === "imessage-photon" ? "send a fresh message to your Photon number" : provider === "telegram" ? "start a fresh conversation with /new and send the test message again" : provider === "github" ? "start a new issue or pull request conversation and mention the agent again" : provider === "microsoft-teams" ? "start a new channel post and mention the agent again" : "send a new root mention to the agent"; - const identityGuidance = principalsQuery.isError + const identityGuidance = provider === "imessage-photon" && principalsQuery.isSuccess && (identities.length === 0 || unlinkedIdentities.length > 0) + ? { tone: "info" as const, title: "Link your Messages identity", body: "Send one message to discover your phone number or Apple account address, then link that exact identity in Access. Send a fresh request after linking; earlier messages do not start work." } + : principalsQuery.isError ? { tone: "warning" as const, title: "Identity readiness could not be checked", @@ -1550,7 +1572,12 @@ function TryStep({ ? `@${normalizedBotUsername}` : (botLabel ?? agentName); const instructions = - provider === "discord" + provider === "imessage-photon" ? [ + photonAllocation === "shared" ? "In your Photon project, enroll your sender in Users and find its assigned number in Get started. Send a fresh message to that number from Apple Messages." : `Open Apple Messages and send a fresh message to ${botUsername ?? botLabel ?? "the dedicated number"}.`, + "Link the discovered sender to a Paperclip person in Access, then send a fresh request.", + "Wait for the agent’s actual reply. Setup completes after that reply is delivered.", + ...(photonAllocation === "shared" ? ["This Pro-compatible channel supports DMs only. Group messages cannot start work."] : ["For a group: add the number in Messages, send a message, enable the discovered group in Settings, then send a fresh request."]), + ] : provider === "discord" ? [ "Open a text channel where the bot is installed.", `Mention ${botMention} in a new root message.`, @@ -1616,6 +1643,7 @@ function TryStep({
) : null} + {provider === "imessage-photon" && botUsername &&
{copyError &&

{copyError}

}
}
    {instructions.map((item) => (
  1. {item}
  2. diff --git a/ui/src/pages/apps/chat/ChatIdentityConfirm.tsx b/ui/src/pages/apps/chat/ChatIdentityConfirm.tsx index 85ebf95ffa..f0f0d0a751 100644 --- a/ui/src/pages/apps/chat/ChatIdentityConfirm.tsx +++ b/ui/src/pages/apps/chat/ChatIdentityConfirm.tsx @@ -14,6 +14,7 @@ const providerNames: Record = { "microsoft-teams": "Microsoft Teams", telegram: "Telegram", agentmail: "AgentMail", + "imessage-photon": "iMessage Photon", }; export function ChatIdentityConfirm() { diff --git a/ui/src/pages/apps/chat/PhotonConnectStep.tsx b/ui/src/pages/apps/chat/PhotonConnectStep.tsx new file mode 100644 index 0000000000..b2e5b33e0a --- /dev/null +++ b/ui/src/pages/apps/chat/PhotonConnectStep.tsx @@ -0,0 +1,192 @@ +import { useState } from "react"; +import { useMutation } from "@tanstack/react-query"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { + chatEndpointsApi, + type ChatEndpoint, + type ChatEndpointSetupAction, +} from "@/api/chatEndpoints"; +import { sanitizedSetupErrorMessage } from "./chat-setup-error"; + +export function PhotonConnectStep({ + endpoint, + agentName, + repairing, + pending, + onAction, +}: { + endpoint: ChatEndpoint; + agentName: string; + repairing: boolean; + pending: boolean; + onAction( + action: ChatEndpointSetupAction, + values?: Record, + ): void; +}) { + const [projectId, setProjectId] = useState(endpoint.providerAccountId ?? ""); + const [projectSecret, setProjectSecret] = useState(""); + const [lineId, setLineId] = useState(""); + const inspection = useMutation({ + mutationFn: () => + chatEndpointsApi.inspectPhoton(endpoint.id, { + projectId: projectId.trim(), + projectSecret, + }), + onSuccess: (result) => { + const eligible = result.lines.filter((line) => line.eligible); + setLineId(eligible.length === 1 ? eligible[0].lineId : ""); + }, + }); + const resetInspection = () => { + inspection.reset(); + setLineId(""); + }; + return ( +
    +
    +

    Connect iMessage Photon

    +

    + Connect {agentName} to Photon Cloud. Pro supports direct messages through + a shared line. Dedicated numbers also support individually enabled groups. +

    +

    + + Photon dashboard + + {" · "} + + Photon line setup + +

    +
    + {repairing && ( +

    + Reconnect keeps this project and{" "} + {endpoint.photonAllocation === "shared" ? "shared DM allocation" : endpoint.botExternalId ?? "dedicated number"}. Leave the secret blank + to reuse the saved connection. +

    + )} + + + + {inspection.isError && ( +

    + {sanitizedSetupErrorMessage(inspection.error, { projectSecret })} +

    + )} + {inspection.data && ( +
    + + {inspection.data.allocation === "shared" ? "Shared DMs" : "Dedicated numbers"} in {inspection.data.projectName} + + {!inspection.data.eligible && ( +

    + {inspection.data.allocation === "shared" + ? "This shared project already belongs to another channel. Use a separate Photon project for each agent." + : "No eligible dedicated number is available. Check the line allocation in Photon and existing Paperclip channels."} +

    + )} + {inspection.data.allocation === "shared" && inspection.data.eligible && ( +

    + Direct messages only. Enroll each test sender in your Photon project's Users page, + then use the number Photon assigns to that sender. Paperclip identity linking is + still required. Groups cannot be enabled on this channel. +

    + )} + {inspection.data.lines.map((line) => ( + + ))} +
    + )} +
    + +
    +
    + ); +} diff --git a/ui/storybook/.storybook/preview.tsx b/ui/storybook/.storybook/preview.tsx index 51592afd83..ebabff6000 100644 --- a/ui/storybook/.storybook/preview.tsx +++ b/ui/storybook/.storybook/preview.tsx @@ -307,6 +307,11 @@ function installStorybookApiFixtures() { ? Response.json({ secretId: "saved-claude-subscription", latestVersion: 1 }) : new Response(null, { status: 404 }); } + if (/^\/api\/companies\/[^/]+\/ai-connections$/.test(url.pathname)) { + return init?.method === "POST" + ? Response.json({ connectionId: "managed-storybook", grantId: "grant-storybook" }) + : Response.json({ currentUserId: "user-storybook", connections: [] }); + } if (/^\/api\/companies\/[^/]+\/me\/user-secrets$/.test(url.pathname)) { return Response.json(onboardingFixtureState.savedApiKeys ? ["ANTHROPIC_API_KEY", "OPENAI_API_KEY"].map((key) => ({ definition: { id: key, companyId: "company-storybook", key: `${key}.setup.storybook`, name: key === "ANTHROPIC_API_KEY" ? "My Claude key" : "My OpenAI key", status: "active" }, diff --git a/ui/storybook/fixtures/aiConnections.ts b/ui/storybook/fixtures/aiConnections.ts new file mode 100644 index 0000000000..d04d9ad13f --- /dev/null +++ b/ui/storybook/fixtures/aiConnections.ts @@ -0,0 +1,139 @@ +import type { + AiConnectionBinding, + AiConnectionRequirement, + AiConnectionSummary, +} from "@/components/ai-connections/model"; + +export const AI_REVIEW_REQUIREMENT: AiConnectionRequirement = { + companyId: "ai-review-company", + provider: "anthropic", + method: "subscription", +}; +export const AI_REVIEW_BINDING: AiConnectionBinding = { + provider: "anthropic", + method: "subscription", + mode: "responsible_user", +}; +export const AI_REVIEW_CONNECTIONS: AiConnectionSummary[] = [ + { + id: "claude-dotta", + grantId: "grant-dotta", + companyId: "ai-review-company", + provider: "anthropic", + method: "subscription", + name: "My Claude subscription", + accountLabel: "dotta@example.test", + ownership: "personal", + ownerUserId: "dotta", + ownerName: "Dotta", + isDefault: true, + status: "connected", + }, + { + id: "claude-second", + grantId: "grant-second", + companyId: "ai-review-company", + provider: "anthropic", + method: "subscription", + name: "My research account", + accountLabel: "research@example.test", + ownership: "personal", + ownerUserId: "dotta", + ownerName: "Dotta", + status: "connected", + }, + { + id: "claude-shared", + grantId: "grant-shared", + companyId: "ai-review-company", + provider: "anthropic", + method: "subscription", + name: "Engineering Claude", + accountLabel: "engineering@example.test", + ownership: "shared", + status: "connected", + }, + { + id: "claude-sam", + grantId: "grant-sam", + companyId: "ai-review-company", + provider: "anthropic", + method: "subscription", + name: "Sam’s Claude subscription", + accountLabel: "sam@example.test", + ownership: "personal", + ownerUserId: "sam", + ownerName: "Sam", + status: "connected", + }, + { + id: "openai-personal", + grantId: "grant-openai", + companyId: "ai-review-company", + provider: "openai", + method: "subscription", + name: "My ChatGPT subscription", + accountLabel: "dotta@example.test", + ownership: "personal", + ownerUserId: "dotta", + ownerName: "Dotta", + isDefault: true, + status: "connected", + }, + { + id: "openai-api", + grantId: "grant-openai-api", + companyId: "ai-review-company", + provider: "openai", + method: "api_key", + name: "Engineering OpenAI API", + ownership: "shared", + status: "connected", + }, + { + id: "claude-api", + grantId: "grant-claude-api", + companyId: "ai-review-company", + provider: "anthropic", + method: "api_key", + name: "Claude API — staging", + ownership: "shared", + status: "needs_attention", + }, + { + id: "openrouter-api", + grantId: "grant-openrouter", + companyId: "ai-review-company", + provider: "openrouter", + method: "api_key", + name: "OpenRouter research", + ownership: "personal", + ownerUserId: "dotta", + ownerName: "Dotta", + isDefault: true, + status: "connected", + }, + { + id: "grok-personal", + grantId: "grant-grok", + companyId: "ai-review-company", + provider: "xai", + method: "subscription", + name: "My Grok subscription", + ownership: "personal", + ownerUserId: "dotta", + ownerName: "Dotta", + isDefault: true, + status: "expired", + }, + { + id: "grok-api", + grantId: "grant-grok-api", + companyId: "ai-review-company", + provider: "xai", + method: "api_key", + name: "Grok API", + ownership: "shared", + status: "revoked", + }, +]; diff --git a/ui/storybook/prototypes/AiConnectionsReview.tsx b/ui/storybook/prototypes/AiConnectionsReview.tsx new file mode 100644 index 0000000000..54400cedbc --- /dev/null +++ b/ui/storybook/prototypes/AiConnectionsReview.tsx @@ -0,0 +1,375 @@ +import { AiReviewBoundary } from "./AiReviewFrame"; +import { AiConnectorPages } from "./AiConnectorPages"; +import { useRef, useState } from "react"; +import { Button } from "@/components/ui/button"; +import { ModelSourceTiles } from "@/components/onboarding/ModelSourceTiles"; +import { + AiConnectionAuth, + type AiAuthState, +} from "@/components/ai-connections/AiConnectionAuth"; +import { AiConnectionPicker } from "@/components/ai-connections/AiConnectionPicker"; +import { + AiConnectionLegacyNotice, +} from "@/components/ai-connections/AiConnectionManagement"; +import { + AI_PROVIDERS, + aiMethodLabel, + bindingProblem, + matchesAiRequirement, + type AiConnectionBinding, + type AiConnectionRequirement, + type AiConnectionSummary, +} from "@/components/ai-connections/model"; +import { + AI_REVIEW_BINDING, + AI_REVIEW_CONNECTIONS, + AI_REVIEW_REQUIREMENT, +} from "../fixtures/aiConnections"; + +export interface AiConnectionsReviewProps { + host?: "onboarding" | "new_agent" | "settings" | "task" | "connections"; + initialStage?: "picker" | "auth" | "manage" | "legacy" | "providers"; + initialConnections?: AiConnectionSummary[]; + initialBinding?: AiConnectionBinding; + requirement?: AiConnectionRequirement; + currentUserId?: string; + initialAuthState?: AiAuthState; + failFirstAttempt?: boolean; + readOnly?: boolean; + loading?: boolean; + error?: string; +} + +/** Storybook-only orchestration. Explicit simulator controls; no provider/network transport. */ +export function AiConnectionsReview(props: AiConnectionsReviewProps) { + if (props.host === "connections") return ; + return ; +} + +function AgentConnectionReview({ + host = "settings", + initialStage = "picker", + initialConnections = AI_REVIEW_CONNECTIONS, + initialBinding, + requirement: initialRequirement = AI_REVIEW_REQUIREMENT, + currentUserId = "dotta", + initialAuthState = { phase: "idle" }, + failFirstAttempt = false, + readOnly, + loading, + error, +}: AiConnectionsReviewProps) { + const requirement = initialRequirement; + const [connections, setConnections] = useState(initialConnections); + const [binding, setBinding] = useState( + initialBinding ?? { + ...AI_REVIEW_BINDING, + provider: requirement.provider, + method: requirement.method, + }, + ); + const [stage, setStage] = useState( + host === "connections" && initialStage === "picker" ? "list" : initialStage, + ); + const [auth, setAuth] = useState(initialAuthState); + const [name] = useState( + `My ${aiMethodLabel(requirement.provider, requirement.method) === "API key" ? `${AI_PROVIDERS[requirement.provider].name} API` : aiMethodLabel(requirement.provider, requirement.method)}`, + ); + const [tested, setTested] = useState(false); + const [adopting, setAdopting] = useState(false); + const [saved, setSaved] = useState(false); + const [hasFailed, setHasFailed] = useState(false); + const [connectionError, setConnectionError] = useState(error); + const returnFocus = useRef(null); + const returnFocusLabel = useRef(null); + const region = useRef(null); + const problem = bindingProblem( + binding, + requirement, + connections, + currentUserId, + "nova", + ); + const titles = { + onboarding: "Connect your model provider", + new_agent: "Connect Nova", + settings: "Nova · Configuration", + task: "Nova needs an AI connection", + connections: "Connections", + }; + const runtime = + requirement.provider === "openai" + ? ["Codex", "Configured OpenAI model"] + : requirement.provider === "anthropic" + ? ["Claude Code", "Configured Claude model"] + : requirement.provider === "xai" + ? ["Grok Build", "Configured Grok model"] + : ["OpenCode", "Configured OpenRouter model"]; + + function restoreFocus() { + requestAnimationFrame(() => { + const target = returnFocus.current?.isConnected + ? returnFocus.current + : Array.from( + region.current?.querySelectorAll("button") ?? [], + ).find((button) => button.textContent === returnFocusLabel.current); + target?.focus(); + }); + } + function openAuth() { + returnFocus.current = document.activeElement as HTMLElement; + returnFocusLabel.current = returnFocus.current?.textContent ?? null; + setAuth({ phase: "idle" }); + setStage("auth"); + } + function connected() { + if (failFirstAttempt && !hasFailed) { + setHasFailed(true); + setAuth({ + phase: "error", + message: + "The provider could not verify this account. Check your credentials and try again.", + }); + return; + } + const id = `review-account-${connections.length + 1}`; + const connection: AiConnectionSummary = { + ...requirement, id, grantId: `grant-${id}`, name: name.trim(), + ownership: "personal", ownerUserId: currentUserId, + ownerName: currentUserId === "dotta" ? "Dotta" : "Sam", + isDefault: !connections.some((row) => matchesAiRequirement(row, requirement) && row.ownerUserId === currentUserId && row.isDefault), + status: "connected", + }; + setConnections((rows) => [...rows, connection]); + setAuth({ phase: "connected" }); + } + + return ( +
    + +
    +

    Example page context · Storybook only

    +
    +

    {titles[host]}

    + {host === "onboarding" && ( +

    + Connect → Configure agent → First task +

    + )} + {host === "task" && ( +

    + Connect an account for the responsible user to continue this task. +

    + )} +
    + {host !== "connections" && ( +
    +
    +
    Harness
    +
    {runtime[0]}
    +
    +
    +
    Model
    +
    {runtime[1]}
    +
    +
    + )} +
    + {stage === "list" && } + {stage === "legacy" && ( + { + setAdopting(true); + setStage("picker"); + }} + /> + )} + {stage === "picker" && ( + <> + {adopting && ( +

    + Confirm this account’s ownership and use, then test it before + replacing existing authentication. +

    + )} + + setConnectionError(undefined)} + onChange={(next) => { + setBinding(next); + setTested(false); + setSaved(false); + }} + onConnect={() => openAuth()} + /> + + {!readOnly && !loading && !connectionError && ( +
    +

    Example form actions · Storybook only

    +
    + {adopting && ( + + )} + +
    +
    + )} + {tested && ( +

    + Connection test passed for{" "} + {currentUserId === "dotta" ? "Dotta" : "Sam"}. Harness and model + are unchanged. +

    + )} + + )} + {stage === "auth" && ( + <> + + ) : null, + }, + ]} + mode={requirement.method === "api_key" ? "api" : "subscription"} + selectedId={requirement.provider} + collapsed + onSelect={() => {}} + /> + + { + if (!name.trim()) return; + setAuth({ + phase: "waiting", + authorizationUrl: "#storybook-provider-simulator", + code: "DEMO-CODE", + }); + }} + onSubmit={() => { + if (name.trim()) connected(); + }} + onCancel={() => { + setAuth({ phase: "cancelled" }); + setStage("picker"); + restoreFocus(); + }} + onDone={() => { + setStage("picker"); + restoreFocus(); + }} + /> + + + )} + {stage === "saved" && ( + <> +

    + {saved + ? adopting + ? "Managed connection adopted." + : "Connection selected for Nova." + : "Connection saved."}{" "} + Harness and model are unchanged. +

    +

    + The account remains in Connections even if you leave agent setup. +

    + + + + )} +
    + ); +} diff --git a/ui/storybook/prototypes/AiConnectorPages.tsx b/ui/storybook/prototypes/AiConnectorPages.tsx new file mode 100644 index 0000000000..54b746e9da --- /dev/null +++ b/ui/storybook/prototypes/AiConnectorPages.tsx @@ -0,0 +1,184 @@ +import { BreadcrumbBar } from "@/components/BreadcrumbBar"; +import { AiReviewBoundary } from "./AiReviewFrame"; +import { AiConnectionAccountControls } from "@/components/ai-connections/AiConnectionAccountControls"; +import { useEffect, useState } from "react"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { Route, Routes, useNavigate, useSearchParams } from "@/lib/router"; +import { APP_DEFINITIONS, getAppStoreDefinition, type AppDefinition, type ToolApplication, type ToolConnection, type ConnectionGrantsResponse } from "@paperclipai/shared"; +import { Browse } from "@/pages/apps/Browse"; +import { AppDetail } from "@/pages/apps/AppDetail"; +import { ConnectionSetupFlow } from "@/features/connections/ConnectionSetupFlow"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { AiConnectionAuth, type AiAuthState } from "@/components/ai-connections/AiConnectionAuth"; +import { CredentialModeLink } from "@/components/onboarding/CredentialModeLink"; +import { AI_PROVIDERS, aiMethodLabel, type AiConnectionSummary, type AiProvider, type AiAuthMethod } from "@/components/ai-connections/model"; +import { AI_REVIEW_CONNECTIONS } from "../fixtures/aiConnections"; +import { storybookAgents } from "../fixtures/paperclipData"; + +const companyId = "company-storybook"; +const date = new Date("2026-09-10T12:00:00Z"); +const agents = storybookAgents.slice(0, 2).map((agent, index) => ({ ...agent, id: index ? "atlas" : "nova", name: index ? "Atlas" : "Nova" })); +const capabilities = { canConfigure: true, canCreateOrganizationGrant: true, canSetCompanyInstall: true, canConnectAsCurrentUser: true, canManageAgentInstalls: true, canViewOtherPersonalIdentities: true, editableAgentIds: ["nova", "atlas"] }; +const members = [{ userId: "dotta", name: "Dotta", email: "dotta@example.test" }, { userId: "sam", name: "Sam", email: "sam@example.test" }]; + +/** Use the production provider catalog; only accounts and actions are simulated. */ +const gallery: AppDefinition[] = (Object.keys(AI_PROVIDERS) as AiProvider[]).map((provider) => getAppStoreDefinition(provider)!); +for (const slug of ["github", "gmail"]) { + const app = getAppStoreDefinition(slug); + if (app) gallery.push(app); +} +function asConnection(account: AiConnectionSummary): ToolConnection { + return { + id: account.id, companyId, applicationId: `app-${account.provider}`, name: account.name, uid: account.id, + connectionKind: "managed", ownership: "customer", connectionPurpose: "ai", transport: "runtime_auth", authKind: account.method === "subscription" ? "oauth" : "api_key", + credentialSource: "paperclip_vault", credentialPolicy: account.ownership === "shared" ? "shared" : "per_user", + status: account.status === "revoked" ? "disabled" : "active", enabled: account.status !== "revoked", + transportConfig: {}, config: { sourceTemplateKey: account.provider, ai: { provider: account.provider, method: account.method }, aiIsolatedSubscription: true }, credentialSecretRefs: [], + healthStatus: account.status === "connected" ? "ok" : "error", healthCheckedAt: date, + healthMessage: account.status === "connected" ? null : "Sign in again to restore this account. No other account will be used.", + lastError: account.status === "connected" ? null : "This account needs to be connected again.", + createdByAgentId: null, createdByUserId: account.ownerUserId ?? "dotta", createdAt: date, updatedAt: date, + }; +} +function grantsFor(account: AiConnectionSummary, readOnly: boolean): ConnectionGrantsResponse { + return { connection: { id: account.id, uid: account.id }, currentUserId: "dotta", members, + capabilities: Object.fromEntries(Object.entries(capabilities).map(([key, value]) => [key, typeof value === "boolean" ? !readOnly && value : value])) as typeof capabilities, + grants: [{ id: account.grantId, companyId, connectionId: account.id, + kind: account.ownership === "shared" ? "organization" : "user", subjectUserId: account.ownerUserId ?? null, + providerTenant: { name: account.accountLabel ?? account.name }, credentialSecretRefs: [], + status: account.status === "connected" ? "active" : account.status === "revoked" ? "revoked" : "expired", + isDefault: account.ownership === "shared", createdByAgentId: null, createdByUserId: account.ownerUserId ?? "dotta", + revokedAt: null, revokedByAgentId: null, revokedByUserId: null, lastUsedAt: null, createdAt: date, updatedAt: date, + members: [], capabilities: { canRevoke: !readOnly && (account.ownership === "shared" || account.ownerUserId === "dotta"), canEditAudience: !readOnly && account.ownership === "shared" }, + }], + }; +} + +/** Mount the production route components against an isolated, deterministic in-memory API. */ +export function AiConnectorPages({ initialConnections = AI_REVIEW_CONNECTIONS, detail = false, readOnly = false }: { + initialConnections?: AiConnectionSummary[]; detail?: boolean; readOnly?: boolean; +}) { + const [client] = useState(() => new QueryClient({ defaultOptions: { queries: { retry: false, staleTime: 0, refetchOnWindowFocus: false }, mutations: { retry: false } } })); + const [store] = useState(() => ({ profiles: new Map(initialConnections.filter((row) => row.id === "claude-dotta").map((row) => [row.id, { + id: `profile-${row.id}`, companyId, profileKey: `app:${row.id}`, entries: [], bindings: ["nova"].map((targetId) => ({ targetType: "agent", targetId })), + }])), accounts: initialConnections.map((row) => ({ ...row })), removed: new Set(), installs: new Map(), audience: new Map() })); + const [ready, setReady] = useState(false); + const [, render] = useState(0); + const navigate = useNavigate(); + function update(account: AiConnectionSummary) { + store.accounts = store.accounts.some((row) => row.id === account.id) ? store.accounts.map((row) => row.id === account.id ? account : row) : [...store.accounts, account]; + void client.invalidateQueries(); render((n) => n + 1); + } + useEffect(() => { + const previous = window.fetch; + window.fetch = async (input, init) => { + const request = input instanceof Request ? input : null; + const url = new URL(request?.url ?? String(input), window.location.origin); + const method = init?.method ?? request?.method ?? "GET"; + const payload = typeof init?.body === "string" ? JSON.parse(init.body) : {}; + const path = url.pathname; + const rows = store.accounts.filter((row) => !store.removed.has(row.id)); + const applications: ToolApplication[] = gallery.map((app) => ({ id: `app-${app.slug}`, companyId, applicationKey: `app-gallery:${app.slug}:review`, name: app.name, description: app.description, type: "mcp_http", status: "active", pluginId: null, ownerAgentId: null, ownerUserId: "dotta", metadata: { sourceTemplateKey: app.slug }, archivedAt: null, createdAt: date, updatedAt: date })); + const reply = (value: unknown) => Promise.resolve(Response.json(value)); + if (path === `/api/companies/${companyId}/tools/gallery`) return reply({ apps: gallery, capabilities: { canCreateOrganizationGrant: !readOnly, organizationGrantReason: null, canSetCompanyInstall: !readOnly, companyInstallReason: null } }); + if (path === `/api/companies/${companyId}/tools/applications`) return reply({ applications }); + if (path === `/api/companies/${companyId}/tools/connections`) return reply({ connections: rows.map(asConnection) }); + if (path === `/api/companies/${companyId}/tools/profiles`) return reply({ profiles: [...store.profiles.values()] }); + if (path === `/api/companies/${companyId}/tools/policies`) return reply({ policies: [] }); + if (path === `/api/companies/${companyId}/agents`) return reply(agents); + if (path === `/api/companies/${companyId}/user-directory`) return reply({ users: members.map((member) => ({ principalId: member.userId, status: "active", user: { name: member.name, email: member.email } })) }); + const match = path.match(/^\/api\/tool-connections\/([^/]+)(.*)$/); + if (match) { + const row = store.accounts.find((candidate) => candidate.id === match[1]); + if (!row) return Response.json({ error: "Unknown review connection" }, { status: 404 }); + const suffix = match[2]; + if (!suffix) { + if (method === "PATCH") update({ ...row, name: payload.name ?? row.name }); + if (method === "DELETE") store.removed.add(row.id); + return reply(asConnection(store.accounts.find((candidate) => candidate.id === row.id)!)); + } + if (suffix === "/grants") { + const result = grantsFor(row, readOnly); + result.grants[0].members = (store.audience.get(row.id) ?? []).map((userId) => ({ id: `member-${userId}`, companyId, grantId: row.grantId, subjectType: "user" as const, subjectId: userId, createdAt: date })); + return reply(result); + } + if (method === "DELETE" && /^\/grants\/[^/]+$/.test(suffix)) { update({ ...row, status: "revoked" }); return reply(grantsFor({ ...row, status: "revoked" }, readOnly).grants[0]); } + if (suffix.endsWith("/members")) { store.audience.set(row.id, payload.memberUserIds ?? []); return reply(grantsFor(row, readOnly).grants[0]); } + if (suffix === "/installs") { + if (method === "PUT") store.installs.set(row.id, payload.installs ?? []); + return reply({ connectionId: row.id, installs: store.installs.get(row.id) ?? [] }); + } + if (suffix === "/catalog") return reply({ catalog: [] }); + return Response.json({ error: `Unsupported review operation: ${suffix}` }, { status: 400 }); + } + if (path.startsWith(`/api/companies/${companyId}/tools/apps/`) && path.endsWith("/finish")) { + const id = path.split("/").at(-2)!; + const profile = { id: `profile-${id}`, companyId, profileKey: `app:${id}`, entries: [], bindings: payload.access === "all_agents" ? [{ targetType: "company", targetId: companyId }] : (payload.access?.agentIds ?? []).map((targetId: string) => ({ targetType: "agent", targetId })) }; + store.profiles.set(id, profile); + return reply({ connection: asConnection(rows.find((row) => row.id === id)!), profile, policy: null }); + } + // Unhandled fixture mutations must never reach a live API/provider. + if (method !== "GET" && path.startsWith("/api/")) return Response.json({ error: "This review does not perform live operations." }, { status: 400 }); + return previous(input, init); + }; + navigate(detail ? `/PAP/apps/${initialConnections[0]?.id ?? "claude-dotta"}/permissions` : "/PAP/apps", { replace: true }); + setReady(true); + return () => { window.fetch = previous; client.clear(); }; + }, []); + if (!ready) return

    Loading Connectors review…

    ; + return +
    +

    Existing app page components below · Fixture accounts · Review annotation

    + +
    + + { + const row = store.accounts.find((account) => account.id === connection.id); + return row ?

    {aiMethodLabel(row.provider, row.method)} · {row.ownership === "shared" ? "Company shared" : "Personal"}{row.isDefault ? " · Personal default" : ""}{row.accountLabel ? ` · ${row.accountLabel}` : ""}

    : null; + }} />} /> + } /> + navigate(`/apps/connect?source=${connection.config?.sourceTemplateKey}&stage=setup&reconnect=${connection.id}`)} renderActions={(connection) => { + const account = store.accounts.find((row) => row.id === connection.id); + return account ? { + store.accounts = store.accounts.map((row) => row.provider === account.provider && row.method === account.method && row.ownerUserId === "dotta" ? { ...row, isDefault: row.id === account.id } : row); update({ ...account, isDefault: true }); + }} + onReconnect={() => navigate(`/apps/connect?source=${account.provider}&stage=setup&reconnect=${account.id}`)} + onRevoke={() => update({ ...account, status: "revoked" })} + /> : undefined; + }} />} /> +
    +
    +
    +
    ; +} + +function Setup({ accounts, onSave }: { accounts: AiConnectionSummary[]; onSave: (account: AiConnectionSummary) => void }) { + const [params] = useSearchParams(); + const navigate = useNavigate(); + const provider = (params.get("source") ?? "anthropic") as AiProvider; + const reconnect = accounts.find((row) => row.id === params.get("reconnect")); + const [method, setMethod] = useState(reconnect?.method ?? (provider === "openrouter" ? "api_key" : "subscription")); + const [state, setState] = useState({ phase: "idle" }); + const [name, setName] = useState(reconnect?.name ?? `My ${AI_PROVIDERS[provider]?.subscriptionName ?? "OpenRouter API"}`); + const [savedId, setSavedId] = useState(); + function complete(grantKind: string, agentIds: string[], allAgents: boolean) { + const id = reconnect?.id ?? `review-${provider}-${accounts.length}`; + const personal = reconnect ? reconnect.ownership === "personal" : grantKind === "user"; + onSave(reconnect ? { ...reconnect, status: "connected" } : { id, grantId: `grant-${id}`, companyId, provider, method, name, ownership: personal ? "personal" : "shared", ownerUserId: personal ? "dotta" : undefined, ownerName: personal ? "Dotta" : undefined, status: "connected", isDefault: personal && !accounts.some((row) => row.ownerUserId === "dotta" && row.provider === provider && row.method === method && row.isDefault) }); + if (!reconnect) void fetch(`/api/companies/${companyId}/tools/apps/${id}/finish`, { method: "POST", body: JSON.stringify({ access: allAgents ? "all_agents" : { agentIds } }) }); + setSavedId(id); setState({ phase: "connected" }); + } + if (!(provider in AI_PROVIDERS)) return <>

    This review focuses on AI authentication. The existing connector remains in the same list.

    ; + return navigate("/apps")} renderCredentialStep={({ grantKind, agentIds, allAgents }) =>
    + + {!reconnect && provider !== "openrouter" && { setMethod(method === "subscription" ? "api_key" : "subscription"); setState({ phase: "idle" }); }} />} + setState({ phase: "waiting", authorizationUrl: "https://example.test/review-authorization", code: provider === "openai" ? "REVIEW-CODE" : undefined })} + onSubmit={() => complete(grantKind, agentIds, allAgents)} + onCancel={() => navigate("/apps")} + onDone={() => navigate(`/apps/${savedId}/permissions`)} /> + {state.phase === "waiting" && method === "subscription" && provider !== "anthropic" && } +
    } />; +} diff --git a/ui/storybook/prototypes/AiReviewFrame.tsx b/ui/storybook/prototypes/AiReviewFrame.tsx new file mode 100644 index 0000000000..35345d29fe --- /dev/null +++ b/ui/storybook/prototypes/AiReviewFrame.tsx @@ -0,0 +1,67 @@ +import type { ReactNode } from "react"; + +/** Review annotations live only in Storybook; none of this frame ships in the app. */ +export function AiReviewFrame({ location, existing, proposed, wrapper, children }: { + location: string; + existing: string; + proposed: string; + wrapper: string; + children: ReactNode; +}) { + return
    + +
    {children}
    +
    ; +} + +/** Marks a precise component boundary within a simulated page or an existing app page. */ +export function AiReviewBoundary({ label, children }: { label: string; children: ReactNode }) { + return
    +

    {label} · Review annotation

    + {children} +
    ; +} + +export function aiReviewContext(id: string, args: { host?: string; initialStage?: string }) { + const story = id.replace("ai-connections-review--", ""); + if (story === "review-index") return { + location: "This is a Storybook review index. It has no app location.", + existing: "The linked stories identify the existing pages they use.", + proposed: "The linked stories identify the new AI-specific components.", + wrapper: "This entire index and its links exist only for review.", + }; + if (story.startsWith("inline-task")) return { + location: "A task → connection request card → Connect / Use existing modal.", + existing: "ConnectionIntentInteractionBody: the task card, modal, reuse chooser and focus handling. ConnectionSetupFlow: access/setup steps.", + proposed: "AI authentication content inside that existing setup flow.", + wrapper: "The example task title, Nova, accounts and successful continuation are simulated. There is no live task or run.", + }; + if (args.host === "connections" || story === "identity-matrix") { + const detail = args.initialStage === "manage"; + return { + location: detail ? "Connectors → choose an account → account permissions/details." : "Connectors (/:company/apps) → provider → Add account or open an account.", + existing: detail ? "AppDetail: header/rename, ownership display and agent-access controls. BreadcrumbBar supplies existing page navigation. The existing revoke dialog and reconnect banner are reused." : "Browse: provider groups, account rows, search and menus. Navigation opens the existing AppDetail and ConnectionSetupFlow components.", + proposed: detail ? "The marked AI account section: personal default and AI credential actions." : "AI provider/account fixture entries and their method/default labels. AI-specific sections are marked when you open an account or sign in.", + wrapper: "This frame, page margins and in-memory API. The page components are real; the AI accounts and all changes are simulated.", + }; + } + const location = args.host === "onboarding" ? "Onboarding → existing provider connection step." + : args.host === "new_agent" ? "Create agent → provider/harness configuration → AI connection field." + : args.host === "task" ? "A task blocked on its responsible user’s AI credentials. This story isolates the picker; see Inline task connection for the real task host." + : "Agent → configuration → AI connection field beside harness/model."; + return { + location, + existing: "ConnectionChoiceList (extracted from connection setup), AppLogo, and the existing onboarding subscription/API-key cards and fields.", + proposed: "The marked AI connection picker and authentication composition. The app uses these controls beside its harness/model settings.", + wrapper: "The Nova heading, harness/model values, save/continue buttons and simulator controls form a mock page. They are not the real agent configuration form.", + }; +} diff --git a/ui/storybook/prototypes/AiTaskConnectionReview.tsx b/ui/storybook/prototypes/AiTaskConnectionReview.tsx new file mode 100644 index 0000000000..4a7797ef13 --- /dev/null +++ b/ui/storybook/prototypes/AiTaskConnectionReview.tsx @@ -0,0 +1,67 @@ +import { AiReviewBoundary } from "./AiReviewFrame"; +import { useEffect, useState } from "react"; +import { QueryClient, QueryClientProvider, useQuery } from "@tanstack/react-query"; +import { APP_DEFINITIONS, type ConnectionIntentInteraction } from "@paperclipai/shared"; +import { ConnectionIntentInteractionBody } from "@/features/connections/ConnectionIntentInteractionBody"; +import { ConnectionSetupFlow, type ConnectionSetupFlowProps } from "@/features/connections/ConnectionSetupFlow"; +import { pendingConnectionIntentInteraction } from "@/fixtures/issueThreadInteractionFixtures"; +import { AiConnectionAuth, type AiAuthState } from "@/components/ai-connections/AiConnectionAuth"; +import { AI_REVIEW_CONNECTIONS } from "../fixtures/aiConnections"; +import { storybookAgents } from "../fixtures/paperclipData"; + +const initial: ConnectionIntentInteraction = { + ...pendingConnectionIntentInteraction, + id: "ai-task-request", companyId: "company-storybook", addresseeUserId: "dotta", + payload: { version: 1, purpose: "ai", serviceSlug: "anthropic", serviceName: "Claude", serviceLogoUrl: "/brands/claude-color.svg", requestingAgentId: "nova", requestingAgentName: "Nova", phase: "requested" }, +}; + +/** The actual task connection request card, modal, reuse flow and focus lifecycle. */ +export function AiTaskConnectionReview({ reuse = false }: { reuse?: boolean }) { + const [client] = useState(() => new QueryClient({ defaultOptions: { queries: { retry: false, refetchOnWindowFocus: false } } })); + const [ready, setReady] = useState(false); + useEffect(() => { + let interaction = initial; + const previous = window.fetch; + window.fetch = async (input, init) => { + const url = new URL(input instanceof Request ? input.url : String(input), window.location.origin); + const path = url.pathname; + const app = APP_DEFINITIONS.find((entry) => entry.slug === "anthropic")!; + const payload = typeof init?.body === "string" ? JSON.parse(init.body) : {}; + if (path.startsWith("/api/connection-intents/ai-task-request/")) { + if (path.endsWith("/complete")) interaction = { ...interaction, status: "accepted", result: { version: 1, outcome: "connected", connectionId: payload.connectionId } }; + if (path.endsWith("/decline")) interaction = { ...interaction, status: "rejected", result: { version: 1, outcome: "declined" } }; + if (path.endsWith("/phase")) interaction = { ...interaction, payload: { ...interaction.payload, phase: payload.phase } }; + return Response.json(path.endsWith("setup-options") ? { + version: 1, interaction, service: { service: "anthropic", name: "Claude", methods: [], state: "needs_user_action", connectionId: null }, + aiConnection: { provider: "anthropic", method: "subscription", mode: "responsible_user" }, requestedAgentId: "nova", existingConnections: reuse ? [{ id: "claude-dotta", applicationId: "app-anthropic", name: AI_REVIEW_CONNECTIONS[0].name, status: "active", enabled: true }] : [], + } : interaction); + } + if (path === "/api/companies/company-storybook/tools/gallery") return Response.json({ apps: [{ ...app, name: "Claude" }], capabilities: { canCreateOrganizationGrant: false, canSetCompanyInstall: false } }); + if (path === "/api/companies/company-storybook/agents") return Response.json([{ ...storybookAgents[0], id: "nova", name: "Nova" }]); + if (path === "/api/ai-review-interactions") return Response.json([interaction]); + return previous(input, init); + }; + setReady(true); + return () => { window.fetch = previous; client.clear(); }; + }, [client, reuse]); + return ready ? : null; +} +function TaskCard() { + const query = useQuery({ queryKey: ["issues", "interactions", "ai-review"], queryFn: async (): Promise => (await fetch("/api/ai-review-interactions")).json() }); + const interaction = query.data?.[0]; + return
    +

    Example task context · Storybook only

    +

    Nova needs your Claude connection

    +

    Existing task connection request · Fixture data. Harness: Claude Code. Model: Configured Claude model.

    + {interaction && } />} +
    ; +} +function TaskSetup(props: ConnectionSetupFlowProps) { + const [state, setState] = useState({ phase: "idle" }); + return setState({ phase: "waiting", authorizationUrl: "https://example.test/review-login" })} + onSubmit={() => setState({ phase: "connected" })} + onCancel={() => props.onCancel?.()} + onDone={() => props.onComplete?.({ connectionId: "review-task-claude" })} + />} />; +} diff --git a/ui/storybook/stories/ai-connections.stories.tsx b/ui/storybook/stories/ai-connections.stories.tsx new file mode 100644 index 0000000000..5f748ea3e8 --- /dev/null +++ b/ui/storybook/stories/ai-connections.stories.tsx @@ -0,0 +1,525 @@ +import { AiReviewFrame, aiReviewContext } from "../prototypes/AiReviewFrame"; +import { AiTaskConnectionReview } from "../prototypes/AiTaskConnectionReview"; +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { expect, userEvent, within } from "storybook/test"; +import { AiConnectionsReview } from "../prototypes/AiConnectionsReview"; +import { AiConnectorPages } from "../prototypes/AiConnectorPages"; +import { + AI_REVIEW_BINDING, + AI_REVIEW_CONNECTIONS, + AI_REVIEW_REQUIREMENT, +} from "../fixtures/aiConnections"; + +const meta = { + title: "AI Connections/Review", + component: AiConnectionsReview, + parameters: { layout: "fullscreen" }, + decorators: [(Story, context) => ( + + )], +} satisfies Meta; +export default meta; +type Story = StoryObj; + +const groups = [ + [ + "Connections", + [ + ["Existing Connectors page with AI providers", "provider-catalog"], + ["Connection list", "connections"], + ["Add account through existing Connectors", "connect-from-existing-catalog"], + ["Provider and identity matrix", "identity-matrix"], + ["Manage account", "management"], + ["Change personal default", "change-personal-default"], + ["Unavailable default", "revoked-default"], + ], + ], + [ + "Choose an account", + [ + ["Responsible user", "responsible-user"], + ["Company shared", "shared-selected"], + ["Human access denied", "shared-audience-denied"], + ["Another responsible user", "another-user-missing"], + ["Incompatible selection", "incompatible-selection"], + ], + ], + [ + "Authentication", + [ + ["Claude subscription", "claude-subscription"], + ["ChatGPT subscription", "chat-gpt-subscription"], + ["Grok subscription", "grok-subscription"], + ["API key", "open-router-api-key"], + ["Invalid credentials and retry", "api-key-retry"], + ["Expired sign-in", "expired-attempt"], + ["Cancel and restore focus", "cancel-and-restore-focus"], + ], + ], + [ + "Complete flows", + [ + ["First onboarding", "first-onboarding"], + ["Onboarding reuse", "onboarding-reuse"], + ["New-agent reuse", "new-agent-reuse"], + ["Inline task connection", "inline-task-connection"], + ["Inline task reuse", "inline-task-reuse"], + ["Settings change", "settings-change"], + ["Legacy adoption", "legacy-adoption"], + ], + ], +] as const; + +export const ReviewIndex: Story = { + render: () => ( +
    +

    AI Connections · Review index

    +

    + Milestone 1: shared UI, simulated accounts, no live authentication. + Start with the existing Connectors page, then account details, provider + sign-in and agent connection selection. Use the Storybook toolbar for light/dark themes and narrow + viewports. +

    +

    + Personal defaults are per company, provider, and sign-in method. + Connection selection never changes harness or model. Unavailable + accounts block without fallback. +

    + {groups.map(([title, links]) => ( +
    +

    {title}

    + {links.map(([label, id]) => ( + + {label} + + ))} +
    + ))} +

    + Additional stories cover read-only, loading, denied access, + reauthorization, revocation, mobile, and unsupported environments. + Runtime enforcement and data migration follow UI review. +

    +
    + ), +}; +export const ProviderCatalog: Story = { + args: { host: "connections", initialStage: "providers" }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await expect(await canvas.findByRole("button", { name: "Add account Anthropic" })).toBeVisible(); + await expect(canvas.getByRole("button", { name: "Connect GitHub" })).toBeVisible(); + await expect(canvas.getByRole("button", { name: "Connect Gmail" })).toBeVisible(); + await userEvent.type(canvas.getByPlaceholderText("Search connectors…"), "Claude"); + await expect(canvas.queryByRole("button", { name: "Connect Gmail" })).not.toBeInTheDocument(); + await userEvent.clear(canvas.getByPlaceholderText("Search connectors…")); + }, +}; +export const ConnectFromExistingCatalog: Story = { + args: { host: "connections" }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await userEvent.click(await canvas.findByRole("button", { name: "Add account Anthropic" })); + await userEvent.click(await canvas.findByRole("radio", { name: "Any agent" })); + await userEvent.click(canvas.getByRole("button", { name: /^(Save and continue|Continue)$/ })); + const name = await canvas.findByLabelText("Connection name"); + await userEvent.clear(name); await userEvent.type(name, "My additional Claude account"); + await userEvent.click(canvas.getByRole("button", { name: "Sign in" })); + await userEvent.type(await canvas.findByLabelText("Authorization code"), "fixture-code"); + await userEvent.click(canvas.getByRole("button", { name: "Submit code" })); + await userEvent.click(canvas.getByRole("button", { name: "Use connection" })); + await expect(await canvas.findByLabelText("AI account settings")).toBeVisible(); + await expect(canvas.getByRole("heading", { name: "My additional Claude account" })).toBeVisible(); + await userEvent.click(canvas.getByRole("link", { name: "Connectors" })); + await expect(await canvas.findByRole("button", { name: "Open My additional Claude account permissions" })).toBeVisible(); + await expect(canvas.getByRole("button", { name: "Open My Claude subscription permissions" })).toBeVisible(); + }, +}; +export const Connections: Story = { args: { host: "connections" } }; +export const IdentityMatrix: Story = { + render: () => , +}; +export const ResponsibleUser: Story = {}; +export const SharedSelected: Story = { + args: { + initialBinding: { + ...AI_REVIEW_BINDING, + mode: "shared", + connectionId: "claude-shared", + grantId: "grant-shared", + }, + }, +}; +export const LegacyPersonalSelectionBlocked: Story = { + args: { + initialBinding: { + ...AI_REVIEW_BINDING, + mode: "delegated", + connectionId: "claude-sam", + grantId: "grant-sam", + }, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await expect(canvas.getByRole("status")).toHaveTextContent("This credential is not shared with you"); + await expect(canvas.queryByRole("button", { name: /Sam’s Claude/ })).not.toBeInTheDocument(); + }, +}; +export const NoAccounts: Story = { args: { initialConnections: [] } }; +export const AnotherUserMissing: Story = { + args: { currentUserId: "sam", host: "task" }, +}; +export const IncompatibleSelection: Story = { + args: { + initialBinding: { + mode: "shared", + provider: "openai", + method: "api_key", + connectionId: "openai-api", + grantId: "grant-openai-api", + }, + }, +}; +export const Loading: Story = { args: { loading: true } }; +export const LoadFailed: Story = { + args: { error: "Could not load AI connections. Try again." }, +}; +export const ReadOnly: Story = { args: { readOnly: true } }; +export const SharedAudienceDenied: Story = { + args: { + initialConnections: AI_REVIEW_CONNECTIONS.map((row) => + row.id === "claude-shared" + ? { + ...row, + unavailableReason: + "The responsible user is not permitted to use this company account.", + } + : row, + ), + }, +}; +export const RevokedDefault: Story = { + args: { + initialConnections: AI_REVIEW_CONNECTIONS.map((row) => + row.id === "claude-dotta" ? { ...row, status: "revoked" } : row, + ), + }, +}; +export const ChangePersonalDefault: Story = { + args: { + host: "connections", initialStage: "manage", + initialConnections: [AI_REVIEW_CONNECTIONS[1], ...AI_REVIEW_CONNECTIONS.filter((row) => row.id !== AI_REVIEW_CONNECTIONS[1].id)], + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await userEvent.click(await canvas.findByRole("button", { name: "Make default" })); + await expect(canvas.getByLabelText("AI account settings")).toHaveTextContent("Personal default"); + await expect(canvas.getByLabelText("AI account settings")).toHaveTextContent("Your default"); + await expect(canvas.queryByRole("button", { name: "Make default" })).not.toBeInTheDocument(); + }, +}; + +const authArgs = { initialStage: "auth" as const }; +const waiting = { + phase: "waiting" as const, + authorizationUrl: "#storybook-provider-simulator", + code: "DEMO-CODE", +}; +export const ClaudeSubscription: Story = { + args: { ...authArgs, initialAuthState: waiting }, +}; +export const ChatGptSubscription: Story = { + args: { + ...authArgs, + requirement: { ...AI_REVIEW_REQUIREMENT, provider: "openai" }, + initialAuthState: waiting, + }, +}; +export const GrokSubscription: Story = { + args: { + ...authArgs, + requirement: { ...AI_REVIEW_REQUIREMENT, provider: "xai" }, + initialAuthState: waiting, + }, +}; +export const ClaudeApiKey: Story = { + args: { + ...authArgs, + requirement: { ...AI_REVIEW_REQUIREMENT, method: "api_key" }, + }, +}; +export const OpenAiApiKey: Story = { + args: { + ...authArgs, + requirement: { + ...AI_REVIEW_REQUIREMENT, + provider: "openai", + method: "api_key", + }, + }, +}; +export const OpenRouterApiKey: Story = { + args: { + ...authArgs, + requirement: { + ...AI_REVIEW_REQUIREMENT, + provider: "openrouter", + method: "api_key", + }, + }, +}; +export const GrokApiKey: Story = { + args: { + ...authArgs, + requirement: { + ...AI_REVIEW_REQUIREMENT, + provider: "xai", + method: "api_key", + }, + }, +}; +export const PreparingLogin: Story = { + args: { ...authArgs, initialAuthState: { phase: "starting" } }, +}; +export const Connected: Story = { + args: { ...authArgs, initialAuthState: { phase: "connected" } }, +}; +export const Cancelled: Story = { + args: { ...authArgs, initialAuthState: { phase: "cancelled" } }, +}; +export const ExpiredAttempt: Story = { + args: { + ...authArgs, + initialAuthState: { + phase: "expired", + message: + "This sign-in attempt expired. Start again to receive a new code.", + }, + }, +}; +export const UnsupportedEnvironment: Story = { + args: { + ...authArgs, + initialAuthState: { + phase: "unsupported", + message: + "Subscription sign-in is unavailable in this environment. Choose an environment that supports this provider’s login.", + }, + }, +}; +export const InvalidCredentials: Story = { + args: { + ...authArgs, + requirement: { ...AI_REVIEW_REQUIREMENT, method: "api_key" }, + initialAuthState: { + phase: "error", + message: + "The provider rejected this API key. Check the key and try again.", + }, + }, +}; +export const ApiKeyRetry: Story = { + args: { + ...authArgs, + initialConnections: [], + requirement: { + ...AI_REVIEW_REQUIREMENT, + provider: "openrouter", + method: "api_key", + }, + failFirstAttempt: true, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await userEvent.type( + canvas.getByLabelText("API key"), + "storybook-not-a-key", + ); + await userEvent.click(canvas.getByRole("button", { name: "Connect" })); + await expect(canvas.getByRole("alert")).toHaveTextContent( + "could not verify", + ); + await expect(canvas.getByLabelText("API key")).toHaveValue(""); + await userEvent.type( + canvas.getByLabelText("API key"), + "storybook-retry-not-a-key", + ); + await userEvent.click(canvas.getByRole("button", { name: "Connect" })); + await expect(canvas.getByRole("status")).toHaveTextContent("Connected."); + await userEvent.click( + canvas.getByRole("button", { name: "Use connection" }), + ); + await expect(canvas.getByText("For you: My OpenRouter API")).toBeVisible(); + }, +}; + +export const FirstOnboarding: Story = { + args: { host: "onboarding", initialConnections: [], initialStage: "auth" }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await userEvent.click(canvas.getByRole("button", { name: "Sign in" })); + await userEvent.type( + canvas.getByLabelText("Authorization code"), + "storybook-code", + ); + await userEvent.click(canvas.getByRole("button", { name: "Submit code" })); + await userEvent.click( + canvas.getByRole("button", { name: "Use connection" }), + ); + await userEvent.click(canvas.getByRole("button", { name: "Continue" })); + await userEvent.click( + canvas.getByRole("button", { + name: "Create another agent using existing connections", + }), + ); + await expect( + canvas.getByText("For you: My Claude subscription"), + ).toBeVisible(); + await expect(canvas.getByTestId("ai-harness")).toHaveTextContent( + "Claude Code", + ); + }, +}; +export const OnboardingReuse: Story = { args: { host: "onboarding" } }; +export const NewAgentReuse: Story = { args: { host: "new_agent" } }; +export const InlineTaskConnection: Story = { + render: () => , + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); const body = within(canvasElement.ownerDocument.body); + await userEvent.click(await canvas.findByRole("button", { name: "Connect" })); + const dialog = within(await body.findByRole("dialog")); + await userEvent.click(await dialog.findByRole("button", { name: /^(Save and continue|Continue)$/ })); + await userEvent.click(await dialog.findByRole("button", { name: "Sign in" })); + await userEvent.type(dialog.getByLabelText("Authorization code"), "fixture-task-code"); + await userEvent.click(dialog.getByRole("button", { name: "Submit code" })); + await userEvent.click(dialog.getByRole("button", { name: "Use connection" })); + await expect(await canvas.findByText("Claude connected")).toBeVisible(); + await expect(canvas.getByTestId("connection-intent-focus-target")).toHaveFocus(); + }, +}; +export const InlineTaskReuse: Story = { + render: () => , + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); const body = within(canvasElement.ownerDocument.body); + await userEvent.click(await canvas.findByRole("button", { name: "Connect / Use existing" })); + const dialog = within(await body.findByRole("dialog")); + await userEvent.click(await dialog.findByRole("button", { name: "My Claude subscription" })); + await expect(await canvas.findByText("Claude connected")).toBeVisible(); + }, +}; +export const SettingsChange: Story = { + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await userEvent.click( + canvas.getByRole("button", { name: "Engineering Claude" }), + ); + await expect( + canvas.getByRole("button", { name: "Engineering Claude" }), + ).toHaveAttribute("aria-pressed", "true"); + await expect(canvas.getByTestId("ai-harness")).toHaveTextContent( + "Claude Code", + ); + await expect(canvas.getByTestId("ai-model")).toHaveTextContent( + "Configured Claude model", + ); + await userEvent.click( + canvas.getByRole("button", { name: "Save connection" }), + ); + await expect(canvas.getByRole("status")).toHaveTextContent( + "Connection selected for Nova", + ); + }, +}; +export const CancelAndRestoreFocus: Story = { + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await userEvent.click( + canvas.getByRole("button", { name: "Connect another account" }), + ); + await userEvent.click(canvas.getByRole("button", { name: "Sign in" })); + await userEvent.click(canvas.getByRole("button", { name: "Cancel" })); + await expect( + canvas.getByRole("button", { name: "Connect another account" }), + ).toHaveFocus(); + await expect( + canvas.getByText("For you: My Claude subscription"), + ).toBeVisible(); + }, +}; +export const Management: Story = { + args: { host: "connections", initialStage: "manage" }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await expect(await canvas.findByRole("heading", { name: "Which humans can use this credential?" })).toBeVisible(); + await expect(canvas.getByRole("heading", { name: "Which agents can use this connection?" })).toBeVisible(); + await expect(canvas.queryByText("Authorized use for other users’ tasks")).not.toBeInTheDocument(); + }, +}; +export const ManagementReadOnly: Story = { + args: { host: "connections", initialStage: "manage", readOnly: true }, +}; +export const ReconnectExisting: Story = { + args: { + host: "connections", + initialStage: "manage", + initialConnections: AI_REVIEW_CONNECTIONS.map((connection) => + connection.id === "claude-dotta" + ? { ...connection, status: "expired" } + : connection, + ), + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await userEvent.click(await canvas.findByRole("button", { name: "Reconnect" })); + await userEvent.click(await canvas.findByRole("button", { name: "Sign in" })); + await userEvent.type(await canvas.findByLabelText("Authorization code"), "fixture-reconnect"); + await userEvent.click(canvas.getByRole("button", { name: "Submit code" })); + await userEvent.click(canvas.getByRole("button", { name: "Use connection" })); + await expect(await canvas.findByRole("heading", { name: "My Claude subscription" })).toBeVisible(); + await expect(canvas.getByLabelText("AI account settings")).toHaveTextContent("Your default"); + }, +}; +export const RevokeConnection: Story = { + args: { host: "connections", initialStage: "manage" }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); const body = within(canvasElement.ownerDocument.body); + await userEvent.click(await canvas.findByRole("button", { name: "Revoke identity" })); + const dialog = within(await body.findByRole("alertdialog")); + await expect(dialog.getByText(/Existing runs may retain credentials/)).toBeVisible(); + await userEvent.click(dialog.getByRole("button", { name: "Revoke identity" })); + await expect(canvas.getByLabelText("AI account settings")).toHaveTextContent("Default unavailable"); + }, +}; +export const LegacyAdoption: Story = { + args: { initialStage: "legacy" }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await userEvent.click( + canvas.getByRole("button", { name: "Choose a managed connection" }), + ); + await expect( + canvas.getByRole("button", { name: "Adopt connection" }), + ).toBeDisabled(); + await userEvent.click( + canvas.getByRole("button", { name: "Test selected connection" }), + ); + await userEvent.click( + canvas.getByRole("button", { name: "Adopt connection" }), + ); + await expect(canvas.getByRole("status")).toHaveTextContent( + "Managed connection adopted.", + ); + }, +}; +export const MobilePicker: Story = { + globals: { viewport: { value: "mobile1", isRotated: false } }, +}; +export const MobileAuthentication: Story = { + args: { ...authArgs, initialAuthState: waiting }, + globals: { viewport: { value: "mobile1", isRotated: false } }, +}; diff --git a/ui/storybook/stories/imessage-photon.stories.tsx b/ui/storybook/stories/imessage-photon.stories.tsx new file mode 100644 index 0000000000..2a517a811b --- /dev/null +++ b/ui/storybook/stories/imessage-photon.stories.tsx @@ -0,0 +1,206 @@ +import { useEffect, useState } from "react"; +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { addons } from "storybook/preview-api"; +import { expect, userEvent, within, waitFor } from "storybook/test"; +import { QueryClient, QueryClientProvider, useQueryClient } from "@tanstack/react-query"; +import { Routes, Route, useNavigate } from "@/lib/router"; +import { ChatEndpointSetup } from "@/pages/apps/chat/ChatEndpointSetup"; +import { ChatEndpointDetail } from "@/pages/apps/chat/ChatEndpointDetail"; +import { ConnectorCard } from "@/pages/apps/Browse"; +import { TaskChatBubble } from "@/components/task-chat/TaskChatBubble"; +import { Button } from "@/components/ui/button"; +import type { ChatEndpoint, ChatIdentityLink } from "@/api/chatEndpoints"; +import type { TaskChatMessageItem } from "@/components/task-chat/task-chat-model"; +import { storybookAgents } from "../fixtures/paperclipData"; + +type Screen = "catalog" | "agent" | "credentials" | "access" | "task" | "reconnect"; +type Scenario = { screen?: Screen; dedicated?: boolean; outage?: boolean; noLine?: boolean; loading?: boolean; connecting?: boolean }; +const endpointId = "photon-story-endpoint"; +const agent = storybookAgents[0]; +const setupUrl = `/apps/chat/new?provider=imessage-photon&purpose=chat&agentId=${agent.id}`; +const sender: ChatIdentityLink = { id: "demo-link", principalId: "demo-person", externalLabel: "+15555550101", status: "pending" }; +let endpoint: ChatEndpoint; +let principal: ChatIdentityLink | null; +let received = false; +let messages: TaskChatMessageItem[] = []; + +function resetFixture(scenario: Scenario) { + const established = ["access", "task", "reconnect"].includes(scenario.screen ?? "catalog"); + endpoint = { + id: endpointId, companyId: agent.companyId, provider: "imessage-photon", + status: established ? "verifying" : "draft", assignedAgentId: agent.id, + assignedAgentName: agent.name, allowDirectMessages: true, allowUnlinkedPeople: false, + allowGroupChats: false, photonAllocation: scenario.dedicated ? "dedicated" : "shared", + setup: { step: established ? "test" : "provider_setup" }, + ...(established ? { providerAccountId: "demo-project", providerAccountLabel: "Demo project", botExternalId: "photon-project:demo-project" } : {}), + }; + principal = established ? { ...sender } : null; + received = false; + messages = []; + if (scenario.screen === "task") { + principal = { ...sender, status: "linked", paperclipUserLabel: "Alex" }; + simulateIncoming(); + } + if (scenario.screen === "reconnect") endpoint.status = "attention"; +} + +function simulateIncoming() { + principal ??= { ...sender }; + if (principal.status !== "linked") return; + received = true; + const followUp = messages.length > 0; + messages.push( + { id: `input-${messages.length}`, kind: "message", author: "human", text: followUp ? "And what about the next step?" : "Help me plan the launch.", sourceChannel: "imessage-photon", timestamp: "2:00 PM" }, + { id: `reply-${messages.length}`, kind: "message", author: "agent", authorName: agent.name, text: followUp ? "Continuing our plan in DEMO-1. Next, review the launch checklist." : "Let’s start with the launch checklist. This conversation is DEMO-1.", timestamp: "2:00 PM" }, + ); +} + +function Journey({ screen = "catalog" }: { screen?: Screen }) { + const navigate = useNavigate(); + const queryClient = useQueryClient(); + const [, rerender] = useState(0); + useEffect(() => { + navigate(screen === "catalog" ? "/apps" : screen === "task" ? "/issues/DEMO-1" : screen === "access" ? `/apps/chat/${endpointId}/access` : `${setupUrl}${screen === "agent" ? "" : `&resume=${endpointId}`}${screen === "reconnect" ? "&reconnect=1" : ""}`, { replace: true }); + }, [screen]); + const refresh = () => { void queryClient.invalidateQueries(); rerender((n) => n + 1); }; + return
    + + + navigate(setupUrl)} onRequestRemove={() => {}} + />
    } /> + } /> + } /> + +

    DEMO-1 · Launch plan

    +

    Each simulated reply completes a turn. Follow-ups stay on DEMO-1. Use /new or /close in Messages when you want a new task.

    + {messages.length ? messages.map((message) => ) :

    Link the sender, then simulate a fresh message to start work.

    } +
} /> + + ; +} + +function Host(props: { screen?: Screen }) { + const [client] = useState(() => new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false } } })); + return ; +} + +const meta = { + title: "Connections/iMessage Photon", component: Host, + parameters: { layout: "fullscreen" }, + beforeEach: ({ parameters }) => { + const scenario = (parameters.photonScenario ?? {}) as Scenario; + resetFixture(scenario); + delete document.body.dataset.photonStoryReady; + delete document.body.dataset.photonStoryError; + const channel = addons.getChannel(); + const report = (error: unknown) => { document.body.dataset.photonStoryError = JSON.stringify(error); }; + channel.on("playFunctionThrewException", report); + channel.on("unhandledErrorsWhilePlaying", report); + const original = window.fetch; + let inspections = 0; + window.fetch = async (input, init) => { + const url = new URL(typeof input === "string" ? input : input instanceof URL ? input.href : input.url, window.location.origin); + if (url.pathname.endsWith("/agents")) return Response.json([agent]); + if (url.pathname === "/api/instance/settings/experimental") return Response.json({ enableChatConnectors: true, enableIsolatedWorkspaces: false }); + if (url.pathname.includes("/chat-endpoints")) { + const body = init?.body ? JSON.parse(String(init.body)) : {}; + if (url.pathname.endsWith("/photon/inspect")) { + if (scenario.loading) return new Promise(() => {}); + if (scenario.outage && inspections++ === 0) return Response.json({ error: "Photon is temporarily unavailable. Try again.", details: { code: "photon_network" } }, { status: 503 }); + return Response.json({ projectId: "demo-project", projectName: "Demo project", allocation: scenario.dedicated ? "dedicated" : "shared", eligible: !scenario.noLine, lines: scenario.dedicated && !scenario.noLine ? [ + { lineId: "line-a", phoneNumber: "+15555550111", eligible: true }, + { lineId: "line-b", phoneNumber: "+15555550112", eligible: true }, + ] : [] }); + } + if (url.pathname.endsWith("/setup")) { + if (scenario.connecting) return new Promise(() => {}); + const number = body.photon?.lineId === "line-b" ? "+15555550112" : "+15555550111"; + endpoint = { ...endpoint, status: "verifying", setup: { step: "test" }, providerAccountId: "demo-project", providerAccountLabel: "Demo project", botExternalId: scenario.dedicated ? number : "photon-project:demo-project", botUsername: scenario.dedicated ? number : null }; + } + if (url.pathname.endsWith("/test")) { + if (!received) return Response.json({ error: "A linked sender must send a fresh test message and receive an agent reply." }, { status: 422 }); + endpoint = { ...endpoint, status: "active", setup: { step: "complete" } }; + } + if (url.pathname.endsWith("/principals")) return Response.json(principal ? [principal] : []); + if (url.pathname.endsWith("/resources") || url.pathname.endsWith("/activity")) return Response.json([]); + if (url.pathname.endsWith("/conversations")) return Response.json(received ? [{ id: "demo-conversation", externalLabel: sender.externalLabel, issueId: "DEMO-1", issueIdentifier: "DEMO-1", issueTitle: "Launch plan", state: "active" }] : []); + if (url.pathname.endsWith("/link-intent")) return Response.json({ confirmationUrl: `${window.location.origin}/simulated-photon-confirmation` }); + if (init?.method === "PATCH") endpoint = { ...endpoint, ...body }; + return Response.json(endpoint); + } + return original(input, init); + }; + return () => { window.fetch = original; channel.off("playFunctionThrewException", report); channel.off("unhandledErrorsWhilePlaying", report); }; + }, + afterEach: ({ id }) => { document.body.dataset.photonStoryReady = id; }, +} satisfies Meta; +export default meta; +type Story = StoryObj; +const step = (screen: Screen, scenario: Scenario = {}): Story => ({ args: { screen }, parameters: { photonScenario: { ...scenario, screen } } }); +const inspect: Story["play"] = async ({ canvasElement }) => { + const canvas = within(canvasElement); + await userEvent.type(await canvas.findByLabelText("Project ID"), "demo-project"); + await userEvent.type(canvas.getByLabelText("Project secret"), "simulated-secret"); + await userEvent.click(canvas.getByRole("button", { name: "Inspect Photon project" })); +}; +export const Catalog: Story = step("catalog"); +export const ChooseAgent: Story = step("agent"); +export const Credentials: Story = step("credentials"); +export const Inspecting: Story = { ...step("credentials", { loading: true }), play: inspect }; +export const Connecting: Story = { ...step("credentials", { connecting: true }), play: async (context) => { + await inspect!(context); await userEvent.click(await within(context.canvasElement).findByRole("button", { name: "Connect shared DMs" })); +} }; +export const MultipleDedicatedNumbers: Story = { ...step("credentials", { dedicated: true }), play: async (context) => { + await inspect!(context); const canvas = within(context.canvasElement); + await expect(await canvas.findByRole("button", { name: "Connect selected number" })).toBeDisabled(); + await userEvent.click(await canvas.findByRole("radio", { name: "+15555550112" })); + await expect(canvas.getByRole("button", { name: "Connect selected number" })).toBeEnabled(); +} }; +export const NoEligibleLine: Story = { ...step("credentials", { dedicated: true, noLine: true }), play: async (context) => { + await inspect!(context); await expect(await within(context.canvasElement).findByRole("alert")).toHaveTextContent("No eligible dedicated number"); +} }; +export const ProviderOutageRecovery: Story = { ...step("credentials", { outage: true }), play: async (context) => { + await inspect!(context); const canvas = within(context.canvasElement); + await expect(await canvas.findByRole("alert")).toHaveTextContent("temporarily unavailable"); + await userEvent.click(canvas.getByRole("button", { name: "Inspect Photon project" })); + await expect(await canvas.findByRole("button", { name: "Connect shared DMs" })).toBeEnabled(); +} }; +export const Reconnect: Story = step("reconnect"); +export const Access: Story = step("access"); +export const IncomingFollowUps: Story = { ...step("task"), play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await waitFor(() => expect(canvas.getByText("Sent from iMessage", { exact: false })).toBeVisible()); + await userEvent.click(canvas.getByRole("button", { name: "Simulate incoming iMessage" })); + await expect(canvas.getByRole("heading", { name: "DEMO-1 · Launch plan" })).toBeVisible(); + await expect(canvas.getAllByText("Sent from iMessage", { exact: false })).toHaveLength(2); +} }; +export const SharedDmWalkthrough: Story = { ...step("catalog"), play: async (context) => { + const canvas = within(context.canvasElement); + await userEvent.click(await canvas.findByRole("button", { name: "Connect iMessage Photon" })); + await userEvent.click(await canvas.findByRole("button", { name: "Continue" })); + await inspect!(context); + await userEvent.click(await canvas.findByRole("button", { name: "Connect shared DMs" })); + await expect(await canvas.findByRole("heading", { name: /Try .* in iMessage Photon/ })).toBeVisible(); + await userEvent.click(canvas.getByRole("button", { name: "Simulate incoming iMessage" })); + await userEvent.click(await canvas.findByRole("button", { name: "Review identity access" })); + await expect(await canvas.findByRole("heading", { name: "External identity access" })).toBeVisible(); + await userEvent.click(canvas.getByRole("button", { name: "Simulate identity confirmation" })); + await userEvent.click(canvas.getByRole("button", { name: "Return to setup" })); + await userEvent.click(canvas.getByRole("button", { name: "Simulate incoming iMessage" })); + await userEvent.click(await canvas.findByRole("button", { name: "I've sent the test message" })); + await expect(await canvas.findByText(/Shared Photon project · direct messages only/)).toBeVisible(); + await userEvent.click(canvas.getByRole("button", { name: "View simulated task" })); + await waitFor(() => expect(canvas.getByText("Sent from iMessage", { exact: false })).toBeVisible()); +} }; +export const NarrowMobile: Story = { ...IncomingFollowUps, globals: { viewport: { value: "mobile1", isRotated: false } } }; diff --git a/ui/storybook/stories/in-feed-connections.stories.tsx b/ui/storybook/stories/in-feed-connections.stories.tsx index 8b084f206b..5cb3aa4977 100644 --- a/ui/storybook/stories/in-feed-connections.stories.tsx +++ b/ui/storybook/stories/in-feed-connections.stories.tsx @@ -31,7 +31,7 @@ const connection = { createdByAgentId: null, createdByUserId: "user-board", createdAt: new Date("2026-09-07"), updatedAt: new Date("2026-09-07"), } satisfies ToolConnection; -type Scenario = { checking?: boolean; count?: number; loading?: boolean; loadError?: boolean; completeError?: boolean; submitting?: boolean; denied?: boolean }; +type Scenario = { ai?: boolean; ownerOnly?: boolean; checking?: boolean; count?: number; loading?: boolean; loadError?: boolean; completeError?: boolean; submitting?: boolean; denied?: boolean }; const meta: Meta = { title: "Connections/In-task connections", parameters: { layout: "padded" }, @@ -45,13 +45,16 @@ const meta: Meta = { channel.on("unhandledErrorsWhilePlaying", reportPlayError); const original = window.fetch; const scenario = (parameters.connectionScenario ?? {}) as Scenario; - let current = structuredClone(pending); + let current = structuredClone(scenario.ai ? aiPending : pending); window.fetch = async (input, init) => { const url = new URL(typeof input === "string" ? input : input instanceof URL ? input.href : input.url, window.location.origin); if (url.pathname.endsWith("/tools/gallery")) return Response.json({ apps: CONNECTABLE_APP_DEFINITIONS.filter((app) => ["notion", "github", "posthog", "zapier"].includes(app.slug)), capabilities: { canCreateOrganizationGrant: true, canSetCompanyInstall: true }, }); + if (scenario.ai && url.pathname.endsWith("/ai-connections") && init?.method === "POST") return scenario.completeError + ? Response.json({ error: "This key could not be verified. Check it and try again." }, { status: 422 }) + : Response.json({ connectionId: aiAccount.id, grantId: aiAccount.grantId }); if (url.pathname.endsWith("/agents")) return Response.json([{ id: pending.payload.requestingAgentId, companyId: pending.companyId, name: pending.payload.requestingAgentName, status: "active", adapterType: "paperclip_runner", role: "researcher" }]); if (url.pathname.startsWith("/api/connection-intents/")) { if (url.pathname.endsWith("setup-options")) { @@ -59,13 +62,14 @@ const meta: Meta = { if (scenario.loadError) return Response.json({ error: "Connection options are temporarily unavailable. Try again." }, { status: 503 }); return Response.json({ version: 1, interaction: current, requestedAgentId: pending.payload.requestingAgentId, service: { service: "notion", name: "Notion", state: "available", methods: [] }, + ...(scenario.ai ? { aiConnection: { provider: "openrouter", method: "api_key", mode: "responsible_user" }, aiRepair: { connection: aiAccount, canReconnect: !scenario.ownerOnly } } : {}), existingConnections: Array.from({ length: scenario.count ?? 0 }, (_, i) => ({ ...connection, id: `${connection.id.slice(0, -1)}${i}`, name: i ? "Team Notion workspace" : connection.name })), }); } if (scenario.submitting) return new Promise(() => {}); if (scenario.completeError || scenario.denied) return Response.json({ error: scenario.denied ? "You no longer have permission to share this connection." : "Connection has no permitted tools. Review action permissions and try again." }, { status: scenario.denied ? 403 : 409 }); if (url.pathname.endsWith("decline")) current = { ...declined, id: pending.id }; - else if (url.pathname.endsWith("complete")) current = { ...connected, id: pending.id }; + else if (url.pathname.endsWith("complete")) current = { ...connected, id: pending.id, payload: current.payload }; else if (url.pathname.endsWith("phase")) current = { ...current, payload: { ...current.payload, phase: "needs_retry" } }; return Response.json(current); } @@ -238,3 +242,49 @@ export const SetupFailureRetry: Story = { ...SetupFailure, play: async (context) await userEvent.click(within(document.body).getByRole("button", { name: /Check link/i })); await expect(await within(document.body).findByText(/Fixture connection could not be verified/)).toBeVisible(); }}; + + +// Storybook supplies only deterministic data/actions. The card and credential +// form below are the production components used inside the task. +const openrouter = CONNECTABLE_APP_DEFINITIONS.find((app) => app.slug === "openrouter")!; +const aiPending: ConnectionIntentInteraction = { ...pending, payload: { ...pending.payload, purpose: "ai", serviceName: "OpenRouter", serviceSlug: "openrouter", serviceLogoUrl: openrouter.branding.logoUrl ?? null, serviceDarkLogoUrl: openrouter.branding.darkLogoUrl ?? null } }; +const aiAccount = { id: connection.id, grantId: "storybook-grant", companyId: pending.companyId, provider: "openrouter", method: "api_key", name: "My OpenRouter account", ownership: "personal", ownerName: "Alex", isDefault: true, status: "revoked" }; +const aiRepair: Story = { + parameters: { connectionScenario: { ai: true } }, + render: () => , + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await userEvent.click(await canvas.findByRole("button", { name: "Fix connection" })); + await expect(await canvas.findByLabelText("Connection name")).toBeDisabled(); + await expect(within(document.body).queryByRole("dialog")).not.toBeInTheDocument(); + }, +}; +export const AiInlineRepair = aiRepair; +export const AiInlineRepairNarrow: Story = { ...aiRepair, globals: { viewport: { value: "mobile1", isRotated: false } } }; +export const AiRepairCancel: Story = { ...aiRepair, play: async context => { + await aiRepair.play!(context); + const canvas = within(context.canvasElement); + await userEvent.click(canvas.getByRole("button", { name: "Cancel" })); + await expect(canvas.queryByTestId("ai-connection-inline-repair")).not.toBeInTheDocument(); + await waitFor(() => expect(canvas.getByTestId("connection-intent-focus-target")).toHaveFocus()); +}}; +export const AiRepairComplete: Story = { ...aiRepair, play: async context => { + await aiRepair.play!(context); + const canvas = within(context.canvasElement); + await userEvent.type(canvas.getByPlaceholderText("Enter API key here"), "storybook-placeholder"); + await userEvent.click(canvas.getByRole("button", { name: "Connect" })); + await expect(await canvas.findByText("OpenRouter connected")).toBeVisible(); +}}; +export const AiRepairInvalidKey: Story = { ...aiRepair, parameters: { connectionScenario: { ai: true, completeError: true } }, play: async context => { + await aiRepair.play!(context); + const canvas = within(context.canvasElement); + await userEvent.type(canvas.getByPlaceholderText("Enter API key here"), "storybook-placeholder"); + await userEvent.click(canvas.getByRole("button", { name: "Connect" })); + await expect(await canvas.findByRole("alert")).toHaveTextContent("This key could not be verified. Check it and try again."); +}}; +export const AiRepairOwnerRequired: Story = { ...aiRepair, parameters: { connectionScenario: { ai: true, ownerOnly: true } }, play: async ({canvasElement}) => { + const canvas = within(canvasElement); + await userEvent.click(await canvas.findByRole("button", { name: "Fix connection" })); + await expect(await canvas.findByText(/Alex must reconnect/)).toBeVisible(); + await expect(canvas.queryByPlaceholderText("Enter API key here")).not.toBeInTheDocument(); +}}; diff --git a/ui/storybook/stories/mobile-entity-pickers.stories.tsx b/ui/storybook/stories/mobile-entity-pickers.stories.tsx new file mode 100644 index 0000000000..50d32adc97 --- /dev/null +++ b/ui/storybook/stories/mobile-entity-pickers.stories.tsx @@ -0,0 +1,58 @@ +import { useEffect, useRef, useState } from "react"; +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { InlineEntitySelector, type InlineEntityOption } from "@/components/InlineEntitySelector"; + +const assignees: InlineEntityOption[] = [ + { id: "agent-product", label: "Product Lead", searchText: "planning product" }, + { id: "agent-engineer", label: "Frontend Engineer", searchText: "ui implementation" }, + { id: "agent-qa", label: "QA Engineer", searchText: "testing review" }, +]; + +const projects: InlineEntityOption[] = [ + { id: "project-control-plane", label: "Control Plane" }, + { id: "project-mobile", label: "Mobile Experience" }, + { id: "project-connectors", label: "Apps and Connectors" }, +]; + +function OpenPicker({ kind, options }: { kind: "Assignee" | "Project"; options: InlineEntityOption[] }) { + const triggerRef = useRef(null); + const [value, setValue] = useState(""); + + useEffect(() => { + triggerRef.current?.click(); + }, []); + + return ( +
+ +
+ ); +} + +const meta = { + title: "Components/Entity pickers/Mobile", + parameters: { + layout: "fullscreen", + viewport: { defaultViewport: "mobile1" }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const AssigneePicker: Story = { + render: () => , +}; + +export const ProjectPicker: Story = { + render: () => , +}; diff --git a/ui/storybook/stories/search.stories.tsx b/ui/storybook/stories/search.stories.tsx index 605c152685..4a3291df9a 100644 --- a/ui/storybook/stories/search.stories.tsx +++ b/ui/storybook/stories/search.stories.tsx @@ -6,7 +6,6 @@ import type { CompanySearchZeroResults, } from "@paperclipai/shared"; import { Badge } from "@/components/ui/badge"; -import { IssueGroupHeader } from "@/components/IssueGroupHeader"; import { Input } from "@/components/ui/input"; import { PageTabBar, type PageTabItem } from "@/components/PageTabBar"; import { MatchSourceChip } from "@/components/search/MatchSourceChip"; @@ -296,58 +295,11 @@ function SearchPagePreview({
{response.results.length} results · sorted by relevance
-
- - {fixtureResults.length} - - } - className="pt-2 pb-1 text-[11px] tracking-wider text-muted-foreground" - /> -
- {fixtureResults.map((result) => ( - - ))} -
-
-
- - {fixtureAgents.length} - - } - className="pt-2 pb-1 text-[11px] tracking-wider text-muted-foreground" - /> -
- {fixtureAgents.map((result) => ( - - ))} -
-
-
- - {fixtureProjects.length} - - } - className="pt-2 pb-1 text-[11px] tracking-wider text-muted-foreground" - /> -
- {fixtureProjects.map((result) => ( - - ))} -
-
+
+ {response.results.map((result) => ( + + ))} +
) : null} @@ -770,7 +722,7 @@ const meta = { docs: { description: { component: - "Full search page surfaces and Command K Search-all handoff. Reuses StatusIcon, StatusBadge, Identity, IssueGroupHeader, and PageTabBar; adds MatchSourceChip + SearchResultRow.", + "Full search page surfaces and Command K Search-all handoff. Reuses StatusIcon, StatusBadge, Identity and PageTabBar; adds MatchSourceChip + SearchResultRow.", }, }, },