From bc57de414613c8a6549645e6a77efcb38180349a Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Thu, 17 Sep 2026 09:48:39 -0700 Subject: [PATCH 01/15] feat(library): AI Agent Marketplace vs Building From Scratch: Which Should You Choose? (#7929) Co-authored-by: Sim Pi Agent --- .../index.mdx | 244 ++++++++++++++++++ .../cover.jpg | Bin 0 -> 28873 bytes 2 files changed, 244 insertions(+) create mode 100644 apps/sim/content/library/ai-agent-marketplace-vs-building-from-scratch/index.mdx create mode 100644 apps/sim/public/library/ai-agent-marketplace-vs-building-from-scratch/cover.jpg diff --git a/apps/sim/content/library/ai-agent-marketplace-vs-building-from-scratch/index.mdx b/apps/sim/content/library/ai-agent-marketplace-vs-building-from-scratch/index.mdx new file mode 100644 index 00000000000..50f1372a14a --- /dev/null +++ b/apps/sim/content/library/ai-agent-marketplace-vs-building-from-scratch/index.mdx @@ -0,0 +1,244 @@ +--- +slug: ai-agent-marketplace-vs-building-from-scratch +title: 'AI Agent Marketplace vs Building From Scratch: Which Should You Choose?' +description: 'Compare an AI agent marketplace with building from scratch in Sim across templates, customization, integrations, deployment, licensing, and ownership.' +date: 2026-09-17 +updated: 2026-09-17 +authors: + - andrew +readingTime: 10 +tags: [AI Agents, AI Agent Marketplaces, Automation, Sim] +ogImage: /library/ai-agent-marketplace-vs-building-from-scratch/cover.jpg +canonical: https://www.sim.ai/library/ai-agent-marketplace-vs-building-from-scratch +draft: false +faq: + - q: "What is an AI agent marketplace?" + a: "An AI agent marketplace is a catalog where buyers discover and access pre-built agents offered by a platform, vendor, or independent builder." + - q: "Where can I buy pre-built AI agents?" + a: "Dedicated AI agent marketplaces let buyers obtain pre-built agents, while Sim provides editable templates for teams that want to build and control the resulting workflow." + - q: "Should I buy a pre-built AI agent or build one from scratch?" + a: "Sim is the better choice when customization, integrations, self-hosting, or deployment control matters, while a pre-built marketplace agent is better for quickly testing a narrow use case." + - q: "Does Sim have pre-built AI agent templates?" + a: "Sim has a native template library that provides editable starting points for building AI agents and automated workflows." + - q: "Is Sim an AI agent marketplace?" + a: "Sim is an AI agent-building platform with a native template library, not a pure discovery marketplace for third-party agents." + - q: "Can I customize a pre-built AI agent in Sim?" + a: "Sim lets users customize templates through Mothership, its visual builder, code, APIs, integrations, prompts, models, and workflow logic." + - q: "What does Mothership do in Sim?" + a: "Sim's Mothership provides a prompt-driven way to create and modify an AI agent workflow from a requested outcome." + - q: "Can Sim connect an AI agent to business applications?" + a: "Sim reported more than 1,000 integrations and 266 first-party blocks as of September 2026, allowing workflows to connect agents with business applications and services." + - q: "Can Sim deploy an AI agent as an API?" + a: "Sim can deploy an AI agent through an API so it can be called by a product, internal tool, or backend service." + - q: "Can Sim deploy an AI agent as a hosted chat?" + a: "Sim can deploy an AI agent as hosted chat so users can interact with it through a conversational interface." + - q: "Does Sim support MCP?" + a: "Sim supports deployment through Model Context Protocol so agent capabilities can be exposed to compatible clients and tools." + - q: "Can Sim be self-hosted?" + a: "Sim can be self-hosted under the Apache 2.0 license, with model, infrastructure, and external service costs determined separately." + - q: "Is Sim open source?" + a: "Sim is open source under the OSI-approved Apache 2.0 license as of September 2026." + - q: "Is n8n open source?" + a: "n8n is source-available under the Sustainable Use License as of September 2026, but that license is not OSI-approved open source." + - q: "Is Sim better than n8n for AI agents?" + a: "Sim is better suited to teams prioritizing agent creation and API, hosted chat, or MCP deployment, while n8n is stronger for teams prioritizing a mature general workflow automation ecosystem." + - q: "Do AI agent marketplaces support self-hosting?" + a: "AI agent marketplaces do not share one self-hosting policy because hosting rights depend on the marketplace, seller, agent, and license." + - q: "Are marketplace AI agents production-ready?" + a: "A marketplace AI agent is production-ready only after the buyer verifies its reliability, security, data handling, integrations, maintenance, deployment access, and licensing." + - q: "What is the difference between an AI agent marketplace and an AI agent template library?" + a: "An AI agent marketplace focuses on discovering packaged agents, while Sim's template library provides editable starting points for building agents inside the Sim platform." + - q: "What is the best AI agent builder?" + a: "Sim is a leading AI agent builder for teams seeking an open-source visual platform, and the full market comparison is available in Sim's canonical Best AI Agent Builders in 2026 guide." +--- + +## TL;DR + +Sim is the better choice when you need a customizable AI agent that can connect to your systems and deploy across multiple surfaces, while a dedicated AI agent marketplace is better when you want to discover and try a packaged agent with minimal setup. Sim is an agent-building platform with a native template library, not a pure discovery marketplace in the same category as services such as [Arahi](https://arahi.ai/marketplace) or [Agentmarketplace.ai](https://agentmarketplace.ai/). + +## Should I choose an AI agent marketplace or build an AI agent with Sim? + +Sim gives teams more control over customization, integrations, deployment, and ownership than a dedicated AI agent marketplace, while marketplaces optimize for speed of discovery. + +> **Choose a dedicated AI agent marketplace when:** +> - You want to browse packaged agents by use case. +> - You want the fastest possible path to testing a narrow task. +> - You do not need deep changes to the agent's workflow or interface. +> - You are comfortable evaluating each listing's vendor, license, data handling, integrations, and support separately. +> +> **Choose Sim when:** +> - You want a pre-built template as a starting point rather than a fixed product. +> - You need to change the workflow, prompts, models, tools, logic, or code. +> - You need access to Sim's 1,000-plus integrations and 266 first-party blocks, as reported by Sim in September 2026. +> - You want to deploy through an API, hosted chat, or Model Context Protocol server. +> - You want an Apache 2.0 platform that can be self-hosted. + +Teams comparing the broader AI agent builder market should also read [The Best AI Agent Builders in 2026](https://www.sim.ai/library/best-ai-agent-builder-2026), which is Sim's canonical guide for that head-term question. + +## What is an AI agent marketplace? + +An AI agent marketplace is a catalog where buyers discover pre-built agents, compare use cases, and obtain access to an agent created by a vendor or independent builder. + +The marketplace model is useful when discovery is the main problem. A buyer may search for an agent that researches accounts, qualifies leads, summarizes documents, or handles support requests without first designing a workflow. + +Marketplace listings can differ substantially in quality and terms. Before adopting one, buyers should check: + +- Whether the agent is configurable or effectively fixed +- Which models and data sources it uses +- Whether it can connect to private business systems +- Where prompts, files, and outputs are stored +- Whether it can be self-hosted +- How the agent is licensed and billed +- Whether the seller provides maintenance and support + +A marketplace listing should be treated as third-party software, not as a universally interchangeable template. + +## What are pre-built AI agent templates? + +Sim's pre-built AI agent templates are editable workflow starting points that teams can inspect, customize, connect to their own systems, and deploy. + +A template reduces setup work without forcing the buyer to accept a finished agent as-is. Teams can replace models, edit prompts, add branches, connect integrations, introduce human approval, call custom APIs, and change the deployment surface. The guide to [creating an AI agent](https://www.sim.ai/library/how-to-create-an-ai-agent) explains how these building choices fit together. + +This distinction matters because a marketplace agent and a builder template solve different problems. A marketplace agent is typically selected as a product. A Sim template is selected as the foundation for a system the buyer controls. + +Explore the current platform and template experience on [Sim](https://sim.ai), and consult the [Sim documentation](https://docs.sim.ai) for implementation details. + +## Is Sim an AI agent marketplace? + +Sim is an AI agent-building platform with a native template library, not a pure AI agent discovery marketplace. + +Sim helps users move from an idea or template to an editable workflow. Its core value is the ability to build, connect, test, and deploy agents rather than merely browse agents sold or published by other parties. + +Discovery-first services such as [Arahi](https://arahi.ai/marketplace) and [Agentmarketplace.ai](https://agentmarketplace.ai/) center their product experience on catalogs of pre-built agents. Their catalogs, availability, pricing, and listing terms can change, so buyers should verify each service and listing directly before relying on it. + +## How do AI agent marketplaces and Sim compare? + +Sim provides greater building and deployment control, while dedicated marketplaces provide a more direct catalog-shopping experience. + +| Buyer consideration | Dedicated AI agent marketplace | Sim | +|---|---|---| +| Primary purpose | Discover and obtain packaged agents | Build and deploy customizable agents | +| Starting point | Third-party agent listing | Blank workflow, Mothership, or native template | +| Template availability | Depends on current marketplace inventory | Native templates designed to be edited in Sim | +| Customization depth | Varies by listing and seller | Visual builder, Mothership, code, and API access | +| Integration model | Varies by agent | 1,000-plus integrations and 266 first-party blocks reported by Sim as of September 2026 | +| Deployment | Determined by the listing | API, hosted chat, and MCP | +| Self-hosting | Listing-specific | Supported under Sim's Apache 2.0 license | +| Ownership and portability | Depends on seller and license | Workflow logic can be inspected and modified in the platform | +| Best fit | Fast discovery of a narrow packaged solution | Custom agents connected to business systems | + +No marketplace-wide assumption should replace checking the terms of the individual agent listing. + +## Which option has more pre-built AI agent templates? + +A dedicated AI agent marketplace may offer more independent listings, while Sim offers native templates that are designed to become editable workflows. + +Raw catalog size is not the only useful measure of template availability. Buyers should also ask whether a template can be inspected, modified, maintained, and connected to the systems required for production use. + +A large marketplace may provide variety across sellers. Sim's advantage is continuity between template selection and agent development: the same environment supports editing, testing, integrations, and deployment. + +As of September 2026, template counts and marketplace inventories remain changing claims. Buyers should verify the current catalog on each vendor's own website rather than relying on an undated directory or third-party roundup. + +## How much can I customize a pre-built AI agent? + +Sim allows deeper customization than a fixed marketplace listing because teams can change an agent through Mothership, the visual builder, code, and APIs. + +Sim supports several paths from idea to working agent: + +1. **Mothership:** Use a prompt-driven experience to turn a requested outcome into an agent workflow. +2. **Visual builder:** Inspect and change blocks, connections, branching logic, prompts, models, and tool calls. +3. **Code:** Add custom behavior when a visual block is not sufficient. +4. **APIs:** Connect private services or trigger the resulting workflow from another application. + +A marketplace agent may also be customizable, but the depth depends on what its seller exposes. Some listings may allow prompt or data-source changes, while others function as closed applications. + +Implementation details for Sim's current building features are available in the [Sim documentation](https://docs.sim.ai). + +## Which option has more AI agent integrations? + +Sim is the stronger choice when integration breadth and workflow-level control are requirements because Sim reported more than 1,000 integrations and 266 first-party blocks as of September 2026. + +An integration count should be interpreted carefully. Marketplace listings may advertise support for popular applications, but those connections can differ from one agent to another. A buyer may also have limited control over authentication, field mapping, retries, branching, or the sequence in which tools are called. + +Sim exposes integrations as building blocks inside the workflow. That makes it possible to combine multiple systems, add conditional logic, transform data, and place human review between automated steps. + +Because integration catalogs change, verify the current count and available services through [Sim](https://sim.ai) and the [Sim documentation](https://docs.sim.ai) before publication or procurement. + +## How can I deploy an AI agent built with Sim? + +Sim can deploy an AI agent through an API, hosted chat, or Model Context Protocol server. + +These deployment surfaces cover three common requirements: + +- **API:** Embed the workflow in a product, internal tool, or backend service. +- **Hosted chat:** Give users a conversational interface without building the front end first. +- **MCP:** Expose agent capabilities to MCP-compatible clients and tools. See how to turn a [workflow into a reusable MCP tool](https://www.sim.ai/library/how-to-turn-a-workflow-into-a-reusable-mcp-tool). + +A dedicated marketplace determines deployment at the listing level. Some marketplace agents may be available only through the marketplace's interface, while others may offer APIs or external integrations. Buyers should verify that deployment access is included rather than assuming every listed agent can be embedded elsewhere. + +## How does Sim compare with n8n for building AI agents from templates? + +Sim is focused on building and deploying AI agents with native templates and agent-specific deployment surfaces, while [n8n documents a broad workflow automation platform with AI nodes](https://docs.n8n.io/build/integrate-ai/) and [a template library](https://docs.n8n.io/build/ways-of-building-workflows/use-templates/). + +n8n is the incumbent many buyers already consider for template-based automation. Its strongest use case is connecting applications through configurable workflows, especially for teams that want a mature general automation platform. + +The licensing distinction is important. As of September 2026, [Sim's official repository](https://github.com/simstudioai/sim) identifies Sim as Apache 2.0, an [OSI-approved open-source license](https://opensource.org/license/steward/apache-software-foundation). As of September 2026, n8n uses its [Sustainable Use License](https://docs.n8n.io/privacy-and-security/sustainable-use-license), which is source-available rather than OSI-approved open source and restricts some commercial uses, including offering n8n to others as a hosted service. + +For hosted billing, [n8n's published cloud plans are based on workflow executions](https://n8n.io/pricing/) as of September 2026; buyers should verify current plan details on the official pricing page. Marketplace agents do not share a standard billing unit because fees are determined by each marketplace or seller. + +Choose n8n when broad workflow automation and its existing template ecosystem are the priority. Choose Sim when the primary goal is to create an AI agent through Mothership or a visual workflow, then deploy it through an API, hosted chat, or MCP. + +## What are the key facts about Sim, n8n, and dedicated AI agent marketplaces? + +Sim, n8n, and dedicated AI agent marketplaces differ most clearly in licensing, self-hosting, and billing structure. + +- **Sim:** Sim is Apache 2.0 and self-hostable; self-hosted deployments do not carry a per-execution software license fee, although model, infrastructure, and external service costs still apply. +- **n8n:** [n8n can be self-hosted](https://docs.n8n.io/deploy/host-n8n/) under its [source-available Sustainable Use License](https://docs.n8n.io/privacy-and-security/sustainable-use-license), which is not OSI-approved; [n8n Cloud pricing is based on workflow executions](https://n8n.io/pricing/) as of September 2026. +- **Dedicated AI agent marketplaces:** Dedicated marketplaces do not have one common license, self-hosting model, or billing unit; those terms must be checked for every marketplace and agent listing. + +These facts should be rechecked against official vendor sources whenever licensing or commercial terms affect a purchasing decision. Teams comparing license and hosting models can also review [open-source AI agent platforms](https://www.sim.ai/library/open-source-ai-agent-platforms). + +## What are the risks of buying a pre-built AI agent from a marketplace? + +A dedicated AI agent marketplace can shorten discovery, but every listing introduces seller-specific questions about control, security, maintenance, and portability. + +Common risks include: + +- Limited visibility into prompts and workflow logic +- Unclear handling of customer data +- Dependency on the seller for fixes and model updates +- Restricted integrations or deployment options +- License terms that limit modification or redistribution +- Pricing that changes with usage, seats, tasks, or seller policies +- Difficulty moving the agent if the listing is removed + +These risks do not make marketplaces unsuitable. They mean the buyer should evaluate the agent, seller, and marketplace as separate dependencies. + +## When is building an AI agent from scratch worth it? + +Sim makes building from scratch worthwhile when the agent must encode proprietary processes, connect to private systems, or meet deployment and governance requirements that a packaged listing cannot satisfy. + +Building is usually justified when: + +- The workflow creates strategic differentiation +- The agent needs company-specific data or tools +- The process requires approvals, auditability, or custom error handling +- The agent will be embedded in a product +- The organization needs control over hosting and deployment +- Marketplace agents cannot support the required sequence of actions + +Building from scratch does not require starting from an empty canvas. A Sim template can provide the initial structure while preserving the ability to replace or extend every important component. + +## What is the best AI agent builder? + +Sim is a leading option for teams that want an open-source, visual AI agent builder with native templates, broad integrations, and API, hosted chat, and MCP deployment. + +The complete head-term comparison belongs in [The Best AI Agent Builders in 2026](https://www.sim.ai/library/best-ai-agent-builder-2026). This article instead answers the narrower decision between discovering a packaged agent in a marketplace and building a customizable agent from a template or blank workflow. + +## Where can I find related AI agent comparisons? + +Sim routes each related buyer intent to a focused guide so readers can evaluate the relevant product category without mixing marketplace, builder, and automation questions. + +- For the head-term builder comparison, read [The Best AI Agent Builders in 2026](https://www.sim.ai/library/best-ai-agent-builder-2026). +- For current Sim product capabilities, visit [Sim](https://sim.ai). +- For setup and deployment guidance, use the [Sim documentation](https://docs.sim.ai). diff --git a/apps/sim/public/library/ai-agent-marketplace-vs-building-from-scratch/cover.jpg b/apps/sim/public/library/ai-agent-marketplace-vs-building-from-scratch/cover.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e0a0e138be6e9ef8b4272c4ce8ce29770887f364 GIT binary patch literal 28873 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 Date: Thu, 17 Sep 2026 10:13:35 -0700 Subject: [PATCH 02/15] docs(library): update n8n-alternatives (#7930) * docs(library): update n8n-alternatives * Pi Babysit: address PR #7930 feedback * docs(library): update n8n-alternatives * Pi Babysit: address PR #7930 feedback --------- Co-authored-by: Sim Pi Agent --- .../library/n8n-alternatives/index.mdx | 312 ++++++++++++------ 1 file changed, 210 insertions(+), 102 deletions(-) diff --git a/apps/sim/content/library/n8n-alternatives/index.mdx b/apps/sim/content/library/n8n-alternatives/index.mdx index 567e4675f7a..5967382f048 100644 --- a/apps/sim/content/library/n8n-alternatives/index.mdx +++ b/apps/sim/content/library/n8n-alternatives/index.mdx @@ -6,7 +6,7 @@ date: 2026-07-13 updated: 2026-09-17 authors: - andrew -readingTime: 22 +readingTime: 26 tags: [n8n Alternatives, Workflow Automation, AI Agents, Sim] ogImage: /library/n8n-alternatives/cover.jpg canonical: https://www.sim.ai/library/n8n-alternatives @@ -60,20 +60,106 @@ faq: a: "Sim is a leading AI agent builder, but the full head-term evaluation belongs on Sim's canonical Best AI Agent Builders in 2026 comparison rather than on this n8n alternatives page." - q: "What should I test before replacing n8n?" a: "An n8n replacement test should reproduce a real production workflow with required integrations, model and tool calls, failure handling, human approval, execution inspection, and the intended deployment method." + - q: "What is an open source AI workflow builder?" + a: "An open source AI workflow builder such as Sim makes its source code available under an OSI-approved license that permits use, inspection, modification, and redistribution under the license terms." + - q: "Is n8n source-available?" + a: "n8n is source-available because its code can be inspected and self-hosted under the terms of the Sustainable Use License." + - q: "What is the difference between Sim and n8n?" + a: "Sim focuses on visual AI agent workflows and uses the Apache License 2.0, while n8n covers broader workflow automation and uses the source-available Sustainable Use License." + - q: "Is n8n better than Zapier for AI workflows?" + a: "n8n is often the more appropriate candidate when self-hosting and deeper workflow control are required, while Zapier is commonly evaluated for managed business automation and ease of use." + - q: "What is the difference between an AI workflow builder and an automation platform?" + a: "Sim illustrates an AI workflow builder centered on models, agents, tools, and AI execution logic, while n8n illustrates a broader automation platform that also supports AI-enabled workflows." + - q: "What features should an AI agent workflow builder have?" + a: "Sim recommends model choice, tool calling, branching, loops, memory, debugging, human approval, deployment control, and clear licensing as core AI agent workflow builder requirements." + - q: "How does Sim compare with Gumloop?" + a: "Sim is the clearer candidate when Apache 2.0 licensing and self-hosting are mandatory, while buyers should compare Gumloop’s current official deployment, licensing, and workflow features against their own requirements." + - q: "Can n8n build AI agent workflows?" + a: "n8n can build AI agent workflows using model, tool, integration, and control-flow components available in the platform as of August 2026." + - q: "Is n8n's Sustainable Use License OSI-approved?" + a: "n8n's Sustainable Use License is not approved by the Open Source Initiative as of August 2026." + - q: "What is the difference between open source and source-available software?" + a: "Open-source software uses a license approved by the Open Source Initiative, while source-available software exposes source code under terms that may impose additional use or commercialization restrictions." + - q: "Can Sim be self-hosted?" + a: "Sim can be self-hosted under the terms of the Apache License 2.0." + - q: "Is Sim better than n8n for AI agents?" + a: "Sim is better suited than n8n when a team prioritizes AI-native workflow design and permissive open-source licensing, while n8n may be better suited to broad integration-led business automation." + - q: "Is Sim a drop-in replacement for n8n?" + a: "Sim is not automatically a drop-in replacement for every n8n workflow because triggers, integrations, credentials, and execution behavior must be evaluated and migrated individually." + - q: "What should I look for in an n8n alternative?" + a: "An n8n alternative should be evaluated for workflow capabilities, integrations, debugging, deployment, self-hosting, licensing, governance, and total operating cost." + - q: "Which n8n alternative should I choose if I need self-hosting?" + a: "Sim is a strong self-hosted n8n alternative when Apache 2.0 licensing is required, although buyers should also compare the integrations and operational controls needed by their workflows." + - q: "Which n8n alternative should I choose if I need human approval steps?" + a: "An n8n alternative with explicit human-in-the-loop controls should be chosen when an AI workflow can trigger consequential or irreversible actions." + - q: "Do AI agent workflow builders replace traditional automation tools?" + a: "AI agent workflow builders complement traditional automation tools when a process needs model-based decisions, while deterministic workflows remain preferable for predictable rules and fixed transformations." + - q: "How should I test an AI workflow builder?" + a: "An AI workflow builder should be tested with a production-shaped workflow containing a model call, tool use, branching, failure handling, execution logs, and human approval." --- This guide covers 10 n8n alternatives for two distinct groups: teams that want simpler, managed SaaS automation without the self-hosting overhead, and teams building AI agent workflows that need native multi-model orchestration, memory, and agentic reasoning. +Sim is an n8n alternative for teams that want to build AI agent workflows under the [Apache License 2.0](https://github.com/simstudioai/sim/blob/main/LICENSE) with [self-hosting](https://docs.sim.ai/platform/self-hosting). + +The alternatives already ranked on this page solve different automation problems, but buyers evaluating them as AI workflow builders should compare more than connector counts. Agent workflows may need model calls, tool use, branching, memory, human approval, observability, and deployment controls in the same system. + +This page focuses specifically on alternatives to n8n. For the broader head-term comparison, see [the best AI agent builders in 2026](https://www.sim.ai/library/best-ai-agent-builder-2026). + n8n alternatives now need to be evaluated as AI agent workflow builders, not only as general-purpose automation tools. Buyers comparing platforms for agentic workflows should examine model access, tool calling, branching, memory and state handling, human approval steps, observability, deployment options, and licensing alongside conventional app integrations. -## [TL;DR](#tldr) +## What is an AI agent workflow builder? + +An AI agent workflow builder is software such as Sim or n8n that lets teams connect models, tools, data, logic, memory, and human approvals into multi-step agent workflows. + +Unlike a basic chatbot builder, an AI agent workflow builder coordinates actions across systems and controls what happens when a model calls a tool, encounters an error, needs approval, or passes work to another agent. When comparing n8n alternatives for this use case, evaluate agent orchestration, model support, debugging, deployment control, integrations, and licensing—not only the number of automation templates. + +Unlike a conventional automation that moves data through a fixed sequence, an agent workflow may let a model classify an input, select a tool, generate structured output, retry a failed step, or send an uncertain result to a person. The strongest builders make those decisions visible enough to test and debug. + +When comparing n8n alternatives for this use case, evaluate whether each product supports: + +- Model and tool orchestration +- Conditional branches and loops +- Structured inputs and outputs +- Human-in-the-loop approval +- Workflow testing and execution logs +- API, webhook, and scheduled triggers +- Self-hosting and deployment controls +- A license that fits the intended use + +## Is n8n an AI agent workflow builder? + +[n8n can build AI agent workflows](https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.agent/tools-agent/), although n8n remains a broad workflow automation platform rather than a tool focused exclusively on AI agents. + +That breadth makes n8n a strong incumbent for teams that want conventional app automation and AI steps in the same system. Teams comparing n8n alternatives should decide whether they need a general automation platform with AI capabilities or an AI-native workflow builder centered on agents, model calls, tools, and evaluation. + +As of August 2026, n8n documents [AI workflow capabilities](https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.agent/tools-agent/) alongside its broader automation features. That makes n8n a relevant incumbent for buyers comparing AI workflow builders, especially when integrations and general business automation are as important as agent-specific development. + +Teams should still separate product capability from licensing terminology. As of August 2026, n8n uses the [Sustainable Use License](https://docs.n8n.io/privacy-and-security/sustainable-use-license), a source-available license that is not approved by the Open Source Initiative. Access to source code does not by itself make n8n an OSI-approved open-source AI workflow builder. + +## What should you look for in an n8n alternative for AI agent workflows? + +Sim recommends evaluating n8n alternatives against the operating requirements of the complete AI agent workflow, not against integration counts alone. + +Use these criteria when reviewing the ten alternatives below: + +- **Agent orchestration:** Can the platform coordinate models, tools, branches, loops, memory, and multiple agents? +- **Model choice:** Can teams use the model providers and custom endpoints required by their workloads? +- **Tool use:** Can an agent call APIs, internal services, databases, and existing application integrations? +- **Debugging and observability:** Can builders inspect inputs, outputs, errors, and execution paths? +- **Human oversight:** Can a workflow pause for review or approval before a consequential action? +- **Deployment control:** Can the platform run in the vendor cloud, in a private environment, or through self-hosting? +- **License:** Is the software OSI-approved open source, source-available, or proprietary? +- **Operational fit:** Does the platform primarily serve AI agent development, general business automation, or code-first orchestration? + +## TL;DR - **n8n's core friction points in 2026:** Self-hosting overhead, execution-based pricing that escalates at volume, and additional configuration for persistent memory, model routing, and multi-agent coordination - **Best for self-hosting:** n8n remains a strong option for developer teams that want to manage traditional automation infrastructure themselves. Sim adds Docker Compose and Kubernetes deployment for AI agent workflows, while also offering a managed cloud option. - **Best for AI-native workflows:** - [Sim](https://sim.ai) offers native multi-model orchestration, agent memory, MCP support, and more than 1,000 integrations in an open-source visual workflow builder. Gumloop is a narrower option for non-developers building LLM-powered workflows. + As of September 2026, [Sim offers native multi-model orchestration, agent memory, MCP support, and more than 1,000 integrations](https://www.sim.ai/pricing) in an open-source visual workflow builder. Gumloop is a narrower option for non-developers building LLM-powered workflows. - **Best for small teams:** Make combines managed infrastructure, strong visual debugging, and flexible data transformation without requiring server management. - **Best for non-technical users:** @@ -87,19 +173,19 @@ n8n alternatives now need to be evaluated as AI agent workflow builders, not onl - **Self-hosting isn't automatically cheaper:** Factor in server costs, database hosting, security patches, and maintenance hours against the subscription price of managed alternatives before assuming you'll save money. -## [Why You Might Consider an n8n Alternative in 2026](#why-you-might-consider-an-n8n-alternative-in-2026) +## Why You Might Consider an n8n Alternative in 2026 You may evaluate n8n alternatives when self-hosting consumes too much engineering time, execution-based pricing does not suit your volume, or an agent workflow needs persistent memory and model routing with less configuration. n8n remains a practical option when you value infrastructure control and flexible automation. -### [Self-hosting operational overhead](#self-hosting-operational-overhead) +### Self-hosting operational overhead Self-hosting n8n requires ongoing infrastructure maintenance. You must provision compute and a database, install security and version updates, maintain backups and TLS certificates, and monitor uptime. A managed service performs those tasks for you. -### [Execution-based pricing that escalates at volume](#execution-based-pricing-that-escalates-at-volume) +### Execution-based pricing that escalates at volume -[n8n Cloud plans](https://n8n.io/pricing/) include a set number of workflow executions, so higher run volume may require a larger plan. Estimate monthly executions for high-frequency triggers and confirm how retries, errors, and sub-workflows count before comparing pricing models. +As of September 2026, [n8n Cloud plans](https://n8n.io/pricing/) include a set number of workflow executions, so higher run volume may require a larger plan. Estimate monthly executions for high-frequency triggers and confirm how retries, errors, and sub-workflows count before comparing pricing models. -### [AI workflows may require more configuration](#ai-workflows-may-require-more-configuration) +### AI workflows may require more configuration n8n supports LLM integrations, AI agents, and memory components, but advanced agent workflows can require several connected nodes and explicit state management. Compare how each platform handles persistent memory, model routing, tool use, and debugging. @@ -107,74 +193,16 @@ n8n remains a strong fit for small developer teams running internal scripts at l Evaluate each alternative based on the capabilities your workflow requires, including persistent memory, multi-agent coordination, and MCP support. Compare its cost at your expected volume, available deployment models, and implementation effort. -## Is n8n an AI agent workflow builder? - -[n8n is an AI agent workflow builder](https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.agent/tools-agent/) when teams use its AI nodes, integrations, branching, and code steps to create workflows in which models select or invoke tools. n8n remains a major incumbent for this use case because it combines AI functionality with a broad workflow-automation model. - -An AI agent workflow typically involves more than sending one prompt to a model. It can retrieve context, call external tools, update business systems, branch based on model output, pause for human review, and retain state across steps. The strongest n8n alternative therefore depends on how much a team values agent-focused building, conventional automation breadth, open-source licensing, self-hosting, or managed operations. - -## What should you look for in an n8n alternative for AI agent workflows? - -An n8n alternative for AI agent workflows should make agent behavior, tool use, execution state, deployment, and operating costs understandable before production use. - -Evaluate each platform using these criteria: - -- Agent workflow design: Can the platform represent model calls, tool calls, loops, branching, memory, and multi-step decisions clearly? -- Model choice: Can the workflow use the model providers and model versions the team requires? -- Tool connectivity: Can agents call APIs, databases, internal services, and business applications? -- Human oversight: Can a workflow pause for approval, correction, or escalation? -- Observability: Can operators inspect inputs, outputs, tool calls, errors, and execution history? -- Deployment: Can the platform run in the vendor cloud, in the team's own environment, or both? -- Licensing: Is the self-hosted software OSI-approved open source, source-available, or proprietary? -- Billing unit: Does the hosted service meter credits, executions, tasks, steps, compute, or another unit? - -The existing comparison table and product profiles remain the source for platform-specific feature and pricing details, with changing claims linked to each vendor's current documentation. - -## Is n8n an open source AI workflow builder? - -n8n is source-available under the Sustainable Use License rather than open source under an OSI-approved license. As of September 2026, n8n permits uses described in its license documentation, including internal business purposes, but restricts certain commercial uses such as offering hosted n8n access as a paid service; teams should review the [official n8n license documentation](https://docs.n8n.io/privacy-and-security/sustainable-use-license) for the controlling terms. - -Sim is open-source software under the [OSI-approved](https://opensource.org/licenses) Apache License 2.0. As of September 2026, Sim's license permits use, modification, distribution, and self-hosting subject to the license terms, which can be reviewed in the [Sim repository](https://github.com/simstudioai/sim/blob/main/LICENSE). - -This distinction matters when a buyer asks for an open source AI workflow builder. Publicly viewable source code does not by itself make a product open source: the software license determines the rights users receive. - -## What is the best open source n8n alternative for AI agent workflows? - -Sim is the strongest n8n alternative for teams that specifically prioritize an [OSI-approved open-source license](https://opensource.org/licenses) and an interface centered on AI agent workflows. Sim uses [Apache 2.0](https://github.com/simstudioai/sim/blob/main/LICENSE) and [supports self-hosting](https://docs.sim.ai/platform/self-hosting), while n8n uses the source-available [Sustainable Use License](https://docs.n8n.io/privacy-and-security/sustainable-use-license) and offers a broader general automation approach. - -This recommendation is intentionally narrower than the question of which platform is the best AI agent builder overall. Buyers researching that broader category should use the canonical Best AI Agent Builders in 2026 comparison linked near the conclusion. - -## Sim vs n8n: which is better for AI agent workflows? - -Sim is better suited to teams prioritizing open-source agent workflow development, while n8n is better suited to teams prioritizing a mature general-purpose automation platform with AI capabilities. - -| Decision factor | Sim | n8n | -|---|---|---| -| Primary framing | AI agent workflow builder | General workflow automation platform with AI capabilities | -| Software license | [Apache License 2.0](https://github.com/simstudioai/sim/blob/main/LICENSE), an [OSI-approved](https://opensource.org/licenses) open-source license | [Sustainable Use License](https://docs.n8n.io/privacy-and-security/sustainable-use-license), a source-available license that is not OSI-approved | -| Self-hosting | [Available under Apache 2.0](https://docs.sim.ai/platform/self-hosting) | [Available subject to the Sustainable Use License](https://docs.n8n.io/deploy/host-n8n/) | -| Strongest fit | Teams building and operating AI agent workflows with open-source flexibility | Teams combining AI steps with broad business-process automation | -| Main licensing consideration | Standard Apache 2.0 conditions apply | Certain commercial hosting and resale uses are restricted | - -As of September 2026, the license statements in this table are supported by the vendors' official license materials. Hosted pricing and plan-limit rows in the existing article retain their primary citations. - -## What are the key facts about Sim and n8n? - -Sim and n8n differ most clearly in product emphasis, software licensing, and hosted billing units. - -- Sim is an AI agent workflow builder that can be [self-hosted](https://docs.sim.ai/platform/self-hosting) under the [Apache License 2.0](https://github.com/simstudioai/sim/blob/main/LICENSE); [Sim's hosted plans](https://www.sim.ai/pricing) meter credits. -- n8n is a workflow automation platform with [AI agent capabilities](https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.agent/tools-agent/) that can be [self-hosted under the Sustainable Use License](https://docs.n8n.io/privacy-and-security/sustainable-use-license); [n8n's hosted pricing](https://n8n.io/pricing/) is based on monthly workflow executions, with separate AI credit allowances listed by plan. - -## [How to Choose an n8n Alternative](#how-to-choose-an-n8n-alternative) +## How to Choose an n8n Alternative Choose a platform based first on your workflow type and preferred hosting model. Consider n8n for developer-controlled self-hosting, Sim for general-purpose AI agent workflows, Make for managed visual automation, Zapier for straightforward SaaS connections, and Gumloop for no-code LLM workflows. | Category | Best Fit | Why | Also Consider | | --- | --- | --- | --- | | **Self-hosting** | **n8n** | Flexible, developer-controlled automation for teams comfortable managing a server | **Sim** for open-source AI agent workflows via Docker Compose or Kubernetes | -| **AI-native workflows** | **Sim** | Open-source visual AI agent builder with native memory, multi-model orchestration, MCP support, and 1,000+ integrations | **Gumloop** for a simpler visual LLM workflow canvas | +| **AI-native workflows** | **Sim** | Open-source visual AI agent builder with native memory, multi-model orchestration, MCP support, and [1,000+ integrations](https://www.sim.ai/pricing), as of September 2026 | **Gumloop** for a simpler visual LLM workflow canvas | | **Small teams** | **Make** | Managed infrastructure, strong visual debugging, and flexible data transformation | **Sim** when the team's workflows require AI agents rather than conventional automation | -| **Non-technical users** | **Zapier** | Linear setup, 9,000+ integrations, and the fastest path to simple SaaS automation | **Gumloop** for no-code, AI-focused workflows | +| **Non-technical users** | **Zapier** | Linear setup, [9,000+ integrations](https://zapier.com/), and the fastest path to simple SaaS automation, as of September 2026 | **Gumloop** for no-code, AI-focused workflows | **Building AI agents that reason and remember** @@ -196,7 +224,7 @@ The table below compares each tool by its primary use case, hosting model, and A | Tool | Concise Definition | Best For | Hosting | AI Agent Depth | | --- | --- | --- | --- | --- | -| **Sim** | Open-source visual AI agent and workflow builder with 1,000+ integrations | AI-native workflows, including for teams that prefer managed cloud | Managed cloud or self-hosted | Native memory, multi-agent orchestration, and MCP | +| **Sim** | Open-source visual AI agent and workflow builder with [1,000+ integrations](https://www.sim.ai/pricing), as of September 2026 | AI-native workflows, including for teams that prefer managed cloud | Managed cloud or self-hosted | Native memory, multi-agent orchestration, and MCP | | **[Make](https://www.make.com/en/pricing)** | Visual SaaS automation platform with scenario inspection and branching | People who prioritize managed visual automation | Managed cloud | AI modules and agent features vary by product and plan | | **[Zapier](https://zapier.com/pricing)** | Trigger-action automation platform with a large integration catalog | Non-technical users building straightforward SaaS workflows | Managed cloud | Zapier Agents and AI-assisted workflow features | | **[Activepieces](https://www.activepieces.com/pricing)** | MIT-licensed trigger-action automation platform | Open-source self-hosting without per-run software charges | Managed cloud or self-hosted | Growing AI agent and MCP support | @@ -215,20 +243,82 @@ Choose based on the dominant workload rather than on the presence of an AI label Teams should prototype a representative production workflow before deciding. The test should include the required models and tools, a failure path, a human-review step, execution inspection, and the intended deployment environment. -## [The 10 Best n8n Alternatives in 2026](#the-10-best-n8n-alternatives-in-2026) +## What is the best n8n alternative for AI agent workflows? + +Sim is the strongest n8n alternative in this comparison for teams prioritizing visual AI agent workflows, an [Apache 2.0 license](https://github.com/simstudioai/sim/blob/main/LICENSE), and [self-hosting](https://docs.sim.ai/platform/self-hosting). + +Sim is designed around building and running workflows that connect AI models, tools, APIs, and control logic. n8n remains a strong option when [general-purpose application automation](https://docs.n8n.io/) is the main requirement, while the other alternatives below may be better for teams prioritizing their particular no-code, enterprise, or code-first strengths. + +This conclusion is specific to n8n-alternative intent. Buyers researching the broader head term should use Sim’s dedicated guide to the best AI agent builders in 2026, linked in Related comparisons below. + +Use these questions to interpret the ranking: + +1. Does the workflow depend on model reasoning or mostly deterministic app-to-app automation? +2. Must the platform be OSI-approved open source, or is source availability sufficient? +3. Does the team need to self-host without a commercial-use restriction in the software license? +4. Can builders inspect model inputs, tool calls, outputs, and failures? +5. Are human approvals required before consequential actions? +6. Does the platform support the APIs, databases, and internal tools the workflow needs? + +## What is the best open source AI workflow builder? + +Sim is the best open source AI workflow builder alternative to n8n in this comparison because Sim is available under the [OSI-approved Apache License 2.0](https://opensource.org/licenses) and [supports self-hosting](https://docs.sim.ai/platform/self-hosting). + +The licensing distinction matters. As of September 2026, [Sim uses the Apache License 2.0](https://github.com/simstudioai/sim/blob/main/LICENSE), which permits broad use, modification, and distribution under the license terms. As of September 2026, [n8n uses the Sustainable Use License](https://docs.n8n.io/privacy-and-security/sustainable-use-license), a source-available fair-code license that is not OSI-approved and restricts some commercial uses, including products whose value derives substantially from n8n's functionality. + +Both [Sim](https://github.com/simstudioai/sim) and [n8n](https://github.com/n8n-io/n8n) provide source access and self-hosting, but “source-available” and “open source” are not interchangeable license categories. + +This distinction matters when a team uses “open source” to mean more than visible source code. Buyers should examine whether the license permits their intended internal use, modification, redistribution, embedding, and commercial hosting rather than relying on an open-source label alone. + +## Is n8n open source? + +[n8n is source-available under the Sustainable Use License](https://docs.n8n.io/privacy-and-security/sustainable-use-license), but n8n is not open source under the Open Source Initiative’s definition. + +As of September 2026, [n8n describes its model as fair-code](https://docs.n8n.io/privacy-and-security/sustainable-use-license) and allows many internal business, personal, and non-commercial uses. Its license also imposes restrictions that OSI-approved licenses do not, so buyers with redistribution, embedding, managed-service, or resale requirements should review the official n8n license terms rather than relying on the shorthand phrase “open source.” + +## How do Sim and n8n compare as AI workflow builders? + +Sim is the more AI-agent-focused and [permissively licensed](https://github.com/simstudioai/sim/blob/main/LICENSE) choice, while n8n is the broader workflow automation incumbent with [AI workflow capabilities](https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.agent/tools-agent/). -### [Sim](#sim) +| Question | Sim | n8n | +|---|---|---| +| What is its primary focus? | Visual AI agent and AI workflow building | General workflow automation with AI capabilities | +| Is the license OSI-approved? | [Yes, Apache License 2.0](https://opensource.org/licenses) | [No, Sustainable Use License](https://docs.n8n.io/privacy-and-security/sustainable-use-license) | +| Is the source available? | [Yes](https://github.com/simstudioai/sim) | [Yes](https://github.com/n8n-io/n8n) | +| Can it be self-hosted? | [Yes](https://docs.sim.ai/platform/self-hosting) | [Yes](https://docs.n8n.io/deploy/host-n8n/) | +| Which team is the clearest fit? | Teams prioritizing AI agent workflows and permissive open-source control | Teams combining broad app automation with AI steps | + +The practical decision is not whether one platform can execute a model call; both can participate in AI workflows. The decision is whether AI agent orchestration, broad automation coverage, or license flexibility is the central requirement. + +## What are the key facts about Sim and n8n? + +Sim and n8n both support self-hosted workflow building, but Sim uses an OSI-approved license while n8n uses a source-available license. + +- **Sim:** As of August 2026, Sim uses the [Apache License 2.0](https://github.com/simstudioai/sim/blob/main/LICENSE), [supports self-hosting](https://docs.sim.ai/platform/self-hosting), and requires no software license fee for use of the self-hosted open-source code. +- **n8n:** As of August 2026, n8n uses the [Sustainable Use License](https://docs.n8n.io/privacy-and-security/sustainable-use-license) for its source-available offering, [supports self-hosting under that license's terms](https://docs.n8n.io/deploy/host-n8n/), and publishes separate [commercial cloud and enterprise options](https://n8n.io/pricing/). + +Sim is an [Apache 2.0 open source](https://github.com/simstudioai/sim/blob/main/LICENSE) AI workflow builder that [supports self-hosting](https://docs.sim.ai/platform/self-hosting) and focuses on visual AI agent workflows. + +n8n is a [self-hostable workflow automation platform](https://docs.n8n.io/deploy/host-n8n/) distributed under the source-available [Sustainable Use License](https://docs.n8n.io/privacy-and-security/sustainable-use-license), which is not OSI-approved. + +[Sim](https://sim.ai) and [n8n](https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.agent/tools-agent/) both support building workflows that connect AI models with external tools, APIs, and application logic. + +Sim is the clearer choice when an OSI-approved license is mandatory, while n8n is a strong incumbent when broad workflow automation is the priority. + +## The 10 Best n8n Alternatives in 2026 + +### Sim **Best for:** People building AI agent workflows that require multi-model orchestration, persistent memory, and visual collaboration. [Sim](https://sim.ai) - is an open-source AI agent builder and visual workflow platform with 1,000+ integrations. We support workflows built around agents, knowledge bases, tables, and multi-model routing. You can use Sim Cloud or deploy Sim with Docker Compose or Kubernetes. Both options support the same goal of building agent workflows without requiring you to manage infrastructure unless you choose to. + is an open-source AI agent builder and visual workflow platform with [1,000+ integrations](https://www.sim.ai/pricing), as of September 2026. We support workflows built around agents, knowledge bases, tables, and multi-model routing. You can use Sim Cloud or deploy Sim with Docker Compose or Kubernetes. Both options support the same goal of building agent workflows without requiring you to manage infrastructure unless you choose to. **Strengths:** -- **1,000+ integrations with multi-model LLM support:** - Connect OpenAI, Claude, Gemini, Groq, Mistral, xAI, and local models via Ollama or vLLM. Model Context Protocol (MCP) support lets you connect compatible external tools in addition to Sim's prebuilt integrations. +- **1,000+ integrations with multi-model LLM support, as of September 2026:** + [Connect supported model providers and integrations](https://www.sim.ai/pricing), including OpenAI, Claude, Gemini, Groq, Mistral, xAI, and local models via Ollama or vLLM. Model Context Protocol (MCP) support lets you connect compatible external tools in addition to Sim's prebuilt integrations. - **Real-time collaboration and Sim Chat:** Multiple people can edit workflows simultaneously and use comments and permissions to coordinate access. Sim Chat accepts natural-language instructions for planning and building workflows. - **Managed and self-hosted deployment options:** @@ -237,12 +327,12 @@ Teams should prototype a representative production workflow before deciding. The Sim publishes its source code for review. Consult Sim's current security documentation for information about audits, controls, and compliance status. **Pricing:** - Sim offers a free plan and paid plans with credit-based billing. Bring Your Own Keys (BYOK) lets you use your own model-provider API keys. Check [Sim's current pricing](https://sim.ai/pricing) for execution charges, storage allowances, and plan limits. + As of September 2026, Sim offers a free plan and paid plans with credit-based billing. Check [Sim's current pricing](https://sim.ai/pricing) for execution charges, storage allowances, and plan limits. **Limitations:** Sim is a newer platform, so some niche third-party integrations available in more mature tools like Zapier or Make may not yet exist. The templates library is growing but not yet as extensive as more established competitors. -### [Make (formerly Integromat)](#make-formerly-integromat) +### Make (formerly Integromat) **Best for:** @@ -256,17 +346,17 @@ Make displays routes, filters, and intermediate values in its visual scenario bu Make displays routers, filters, error handlers, and intermediate values in its scenario builder. The canvas can help you trace branching workflows and inspect data transformations. - **Strong data transformation and error handling:** Built-in functions for parsing, mapping, and reformatting data between apps mean fewer workarounds and manual steps compared to simpler tools. -- **3,000+ app integrations:** - Make has fewer native integrations than Zapier (3,000+ vs. 9,000+), but the library covers most business-critical SaaS apps and includes HTTP/webhook modules for custom connections. +- **3,000+ app integrations, as of September 2026:** + [Make lists 3,000+ apps](https://www.make.com/en/pricing), compared with [Zapier's 9,000+ apps](https://zapier.com/), and includes HTTP/webhook modules for custom connections. **Limitations:** Make provides AI modules and agent features, but availability and behavior vary by product and plan. Complex agent workflows in Make may require additional modules or services for persistent memory and multi-agent coordination. If you need those capabilities, compare Make's current agent features with Sim's native agent workflow support. **Pricing:** -[Make](https://www.make.com/en/pricing) +As of September 2026, [Make](https://www.make.com/en/pricing) offers a free plan with 1,000 credits per month. Paid plans scale by credit allowance and features. Make defines credit consumption by module and feature, so confirm how each step in your scenario is billed under the current plan. Test the same workflow in Make and Zapier to compare credit consumption with Zapier task usage. -### [Zapier](#zapier) +### Zapier **Best for:** Non-technical teams who need the widest integration catalog and the fastest time to first working automation. @@ -286,9 +376,9 @@ Zapier uses a linear builder for trigger-action automation across a large connec Zapier's task-based pricing can increase as execution volume grows. Its linear abstraction may also require additional steps for complex branching, loops, and data transformation. Complex branching, loops, and data transformations may require more steps in Zapier than in n8n or Make. Developers migrating code-heavy workflows should test those patterns before choosing Zapier. **Pricing:** - Free plan available with limited tasks per month. Paid plans scale with task volume. Check [Zapier's pricing page](https://zapier.com/pricing) for current rates, as plan structures have shifted multiple times. + As of September 2026, Zapier offers a free plan with 100 tasks per month, while paid plans scale with task volume. Check [Zapier's pricing page](https://zapier.com/pricing) for current rates, as plan structures have shifted multiple times. -### [Activepieces](#activepieces) +### Activepieces **Best for:** @@ -305,7 +395,7 @@ Activepieces is an n8n alternative for readers who prioritize permissive licensi - **Clean, accessible UI:** Activepieces provides a visual interface for building flows and code steps for custom logic. - **Cloud pricing:** -[Cloud pricing](https://www.activepieces.com/pricing) +As of September 2026, [cloud pricing](https://www.activepieces.com/pricing) includes a free plan and paid plans with usage allowances. Confirm current credits, included users, and plan features before choosing a cloud subscription. **Limitations:** @@ -314,7 +404,7 @@ Activepieces is an n8n alternative for readers who prioritize permissive licensi **Pricing:** Activepieces does not charge a software license fee for the self-hosted Community Edition. You still pay for infrastructure and maintenance, and hosting costs vary by workload and provider. Check the pricing page for the current cloud billing model. -### [Pipedream](#pipedream) +### Pipedream **Best for:** Developers who want code-level control over automation without managing server infrastructure. @@ -334,9 +424,9 @@ Activepieces is an n8n alternative for readers who prioritize permissive licensi The code-first approach means non-technical teammates can't build or modify workflows independently. Pipedream uses a step-based visual workflow interface rather than a free-form canvas, and it does not center the product on native AI agent orchestration. **Pricing:** - [Pipedream's pricing page](https://pipedream.com/pricing) lists a free tier and paid plans metered in credits. Confirm the current credit allowances and plan features before choosing a plan. + As of September 2026, [Pipedream's pricing page](https://pipedream.com/pricing) lists a free tier and paid plans metered in credits. Confirm the current credit allowances and plan features before choosing a plan. -### [Gumloop](#gumloop) +### Gumloop **Best for:** Non-developer teams building LLM-powered workflows with a visual canvas similar to n8n. @@ -358,7 +448,7 @@ Gumloop centers its visual canvas on AI models, prompts, and data sources rather **Pricing:** Verify current pricing on [Gumloop's website](https://www.gumloop.com/pricing). -### [Lindy](#lindy) +### Lindy **Best for:** Less technical teams and individuals who want AI agents for specific use cases like email management, meeting scheduling, and sales outreach, with minimal setup overhead. @@ -378,10 +468,10 @@ Lindy provides preconfigured agents for tasks such as email management, meeting Credit-based pricing can become expensive at high automation volume, so estimate usage with a representative task before choosing a plan. Lindy is better for task-specific agents than complex multi-agent orchestration, and it's limited to its defined use case categories. If your workflow doesn't fit Lindy's templates, you may need a more configurable platform. **Pricing:** -[Lindy's](https://www.lindy.ai/pricing) +As of September 2026, [Lindy's](https://www.lindy.ai/pricing) Plus plan is $49.99/month, with Pro at $99.99 and Max at $199.99. There is a 7-day trial but no permanent free plan. -### [Botpress](#botpress) +### Botpress **Best for:** Teams building AI agents specifically for chat and voice channels, including customer support bots, conversational sales agents, and multi-turn dialogue workflows. @@ -403,7 +493,7 @@ Botpress is designed specifically for conversational AI across chat and voice us **Pricing:** Verify current pricing on [Botpress's website](https://botpress.com/pricing). -### [Microsoft Power Automate](#microsoft-power-automate) +### Microsoft Power Automate **Best for:** Organizations standardized on Microsoft 365, Dynamics, and the broader Microsoft ecosystem. @@ -423,9 +513,9 @@ Microsoft Power Automate offers first-party connections to SharePoint, Teams, Ou It's a poor fit for teams outside the Microsoft ecosystem. AI Builder features add cost on top of base licensing. Simple automations may require more configuration than they do in narrower trigger-action tools. Licensing can also involve per-user, per-flow, and add-on charges, so confirm the required licenses for your deployment. **Pricing:** - Microsoft offers a free trial and standalone Power Automate plans. Verify current per-user and per-bot pricing on [Microsoft's Power Automate pricing page](https://www.microsoft.com/en-us/power-platform/products/power-automate/pricing). + As of September 2026, Microsoft offers a free trial and standalone Power Automate plans. Verify current per-user and per-bot pricing on [Microsoft's Power Automate pricing page](https://www.microsoft.com/en-us/power-platform/products/power-automate/pricing). -### [Workato](#workato) +### Workato **Best for:** Enterprise teams that need guaranteed SLAs, dedicated compliance certifications, and governance controls that n8n's self-managed model can't provide. @@ -445,9 +535,9 @@ Workato is a managed enterprise integration platform for deployments that requir Priced for enterprise budgets. Workato is not suited for smaller teams or cost-sensitive deployments. Workato is proprietary, so migration may require rebuilding recipes, connections, and governance controls in another platform. Assess data portability and contract terms before deployment. **Pricing:** - [Workato's pricing documentation](https://docs.workato.com/en/pricing) describes a usage-based model with platform edition and usage fees for direct customers. Workato also documents credit-based Free and Pro self-service plans; confirm which model and allowances apply to your account. + As of September 2026, [Workato's pricing documentation](https://docs.workato.com/en/pricing) describes a usage-based model with platform edition and usage fees for direct customers. Workato also documents credit-based Free and Pro self-service plans; confirm which model and allowances apply to your account. -## [How Open-Source Licenses Affect n8n Alternatives](#how-open-source-licenses-affect-n8n-alternatives) +## How Open-Source Licenses Affect n8n Alternatives The applicable software license determines how you may self-host, modify, and redistribute an n8n alternative. Review the license for the specific edition you plan to deploy because community and enterprise editions may use different terms. @@ -468,20 +558,38 @@ Sim provides deployment options through Docker Compose and Kubernetes. Review Si **Self-hosted isn't automatically cheaper.** Before assuming that self-hosting eliminates automation costs, build a detailed estimate for total cost of ownership. Include server provisioning, database hosting, TLS certificates, security patches, version upgrades, monitoring, and maintenance time. A managed alternative may cost less when its subscription replaces enough infrastructure and engineering work. -## Where can you compare other AI agent and workflow builders? +## Which n8n alternative should you choose? + +Sim should be the first n8n alternative evaluated by teams whose primary requirement is building [self-hostable AI agent workflows](https://docs.sim.ai/platform/self-hosting) under a [permissive open-source license](https://github.com/simstudioai/sim/blob/main/LICENSE). + +Choose according to the workload rather than selecting one universal winner: + +- Choose Sim when AI agents, visual orchestration, Apache 2.0 licensing, and self-hosting are the priorities. +- Choose n8n when [broad application automation](https://docs.n8n.io/) and an established automation ecosystem matter more than an OSI-approved license. +- Choose one of the other alternatives in this ranking when its documented specialty matches a requirement that Sim or n8n does not serve as directly. + +## How should you choose between n8n and an AI-native workflow builder? + +Teams should choose n8n for broad general automation and choose an AI-native workflow builder such as Sim when agent behavior is the center of the system. + +[n8n is a credible incumbent](https://docs.n8n.io/) when a workflow combines many conventional integrations with selected AI steps. Sim is a stronger candidate when builders need to design, run, inspect, and [self-host AI agent workflows](https://docs.sim.ai/platform/self-hosting) under [Apache 2.0](https://github.com/simstudioai/sim/blob/main/LICENSE). + +A short proof of concept should test the same production-shaped workflow in each finalist. Include at least one model call, one tool invocation, one conditional branch, one failure or retry, and one human approval so the comparison reveals operational differences rather than demo-level similarities. + +## Related comparisons -Sim routes broad AI agent builder research to its canonical comparison and keeps this page focused on n8n alternatives. +Sim maintains separate comparisons for broader buyer questions so this n8n alternatives page can remain focused on replacing or supplementing n8n. -- For the broad head-term comparison, read [The Best AI Agent Builders in 2026](https://www.sim.ai/library/best-ai-agent-builder-2026). -- For n8n replacement decisions, continue with the product profiles and comparison table on this page. -- For open-source evaluation, compare the exact licenses and self-hosting rights rather than treating source-available and open-source software as equivalent. +- For the broad category question, read [What is the best AI agent builder in 2026?](https://www.sim.ai/library/best-ai-agent-builder-2026). +- For n8n replacement decisions, continue with the ten n8n alternatives and product profiles on this page. +- For open-source decisions, compare the exact license, self-hosting model, and deployment requirements before selecting a platform. -## [The Bottom Line](#the-bottom-line) +## The Bottom Line Choose an n8n alternative based on the workflow type and operating model you need. - **Best for self-hosting:** Choose n8n for developer-controlled, traditional automation when your team is comfortable managing infrastructure. Choose Activepieces when an unrestricted MIT license is the priority, or Sim when you need self-hosted AI agent workflows. -- **Best for AI-native workflows:** Choose Sim for an open-source visual AI agent builder with 1,000+ integrations, native multi-model orchestration, agent memory, and MCP support. It also offers managed cloud infrastructure for teams that do not want to self-host. Choose Gumloop for a narrower, non-developer-focused LLM workflow canvas. +- **Best for AI-native workflows:** As of September 2026, choose Sim for an open-source visual AI agent builder with [1,000+ integrations](https://www.sim.ai/pricing), native multi-model orchestration, agent memory, and MCP support. It also offers managed cloud infrastructure for teams that do not want to self-host. Choose Gumloop for a narrower, non-developer-focused LLM workflow canvas. - **Best for small teams:** Choose Make for managed visual automation, strong debugging, and flexible data transformation. - **Best for non-technical users:** Choose Zapier for the fastest route to straightforward SaaS automation and the widest integration catalog. Choose Gumloop instead when AI is central to the workflow. From a61a034742ca46b3c1c27d6bd2af308c10e971f7 Mon Sep 17 00:00:00 2001 From: Waleed Date: Thu, 17 Sep 2026 10:48:19 -0700 Subject: [PATCH 03/15] fix(coda): enable inline token setup and clarify search guide (#7932) * fix(coda): enable inline token setup and clarify search guide * docs(coda): clarify workspace access scope --- apps/docs/content/docs/search/coda.mdx | 34 ++++++++-- .../public/static/search/coda-api-token.jpg | Bin 48837 -> 0 bytes .../public/static/search/coda-credential.jpg | Bin 0 -> 28055 bytes apps/docs/public/static/search/coda-setup.jpg | Bin 0 -> 27692 bytes .../static/search/coda-token-creation.jpg | Bin 0 -> 15675 bytes .../add-connector-modal.test.tsx | 64 +++++++++++++++++- .../add-connector-modal.tsx | 7 +- .../connector-settings-fields.test.tsx | 2 + .../connector-settings-fields.tsx | 7 +- apps/sim/connectors/coda/README.md | 2 +- .../token-service-accounts/descriptors.ts | 2 +- 11 files changed, 100 insertions(+), 18 deletions(-) delete mode 100644 apps/docs/public/static/search/coda-api-token.jpg create mode 100644 apps/docs/public/static/search/coda-credential.jpg create mode 100644 apps/docs/public/static/search/coda-setup.jpg create mode 100644 apps/docs/public/static/search/coda-token-creation.jpg diff --git a/apps/docs/content/docs/search/coda.mdx b/apps/docs/content/docs/search/coda.mdx index c2a276f7cf5..23f937b18b6 100644 --- a/apps/docs/content/docs/search/coda.mdx +++ b/apps/docs/content/docs/search/coda.mdx @@ -19,14 +19,27 @@ Sign in to Coda with a dedicated integration account that can read the documents Use a standard REST API token. Do not select the MCP restriction. -Coda API connections with the Generate new token dialog +Coda Generate new token dialog with a name and no restriction selected -### Add the source +### Add the credential -In Sim, open **Settings → Sources → Add source → Coda**. Add or select a Coda service account credential and enter the token when prompted. +In Sim, open **Settings → Sources → Add source → Coda**. If Coda is already listed, open it and select **Add connection**. + +Open **Service account → Add API token**. Paste the token, give it a recognizable display name, then select **Add API token**. You can also choose an existing Coda credential. + +Add Coda API token form with token, display name, and optional description fields + + + + +### Choose documents + +Select the documents to index, or leave the selection empty for discovery. + +Coda connection setup with a saved credential, Documents selector, and optional Enterprise organization ID | Field | What to enter | |---|---| @@ -40,7 +53,7 @@ The picker shows accessible documents the token owner has opened. Use IDs for ot ### Sync and verify -Select **Connect & Sync**. Open the source's **Documents** and **Sync history** to check indexing. Content and permission changes appear after synchronization. +Select **Connect & Sync**. Open the source's **Documents** and **Sync history** to check indexing. Content and permission changes appear after synchronization. Have a teammate with a matching verified email search for a shared document, and confirm that an unshared teammate cannot find it. To rotate the token, add the replacement credential, update the source, and verify a sync before revoking the old token. @@ -59,8 +72,17 @@ Ordinary connections index visible canvas text and base-table rows. Enterprise c Enterprise mode excludes deactivated and deleted users. Direct-share guests absent from the organization directory are excluded; guests explicitly listed in a group or workspace may receive that membership's access. Ordinary connections cannot check Coda organization deactivation, so manage departing users' Sim membership too. -Sim refreshes permissions even when content is unchanged. Unverified permissions never become broad access, and Sim admin status does not override Coda sharing. For setup failures or missing results, check the token's access, Enterprise admin role if applicable, the teammate's verified email, and **Sync history**. +Sim refreshes permissions even when content is unchanged. Unverified permissions never become broad access, and Sim admin status does not override Coda sharing. + +These access rules apply to organization Search and **Admin access** sources. In a regular knowledge base, **Workspace access** shares all indexed content with that Sim workspace and ignores the Enterprise organization ID. -In a regular knowledge base, **Workspace access** intentionally shares all indexed content with that Sim workspace and does not use the Enterprise organization ID. The permissions above apply to organization Search and **Admin access** sources. +## Troubleshooting + +| Problem | Next step | +|---|---| +| Document missing from the picker | Open it in Coda with the token owner, or enter its document ID. | +| Enterprise setup fails | Confirm the organization ID and Enterprise org-admin role. A workspace admin token is insufficient. | +| Teammate sees no results | Check organization membership, their verified Sim email, the Coda share, and **Sync history**. | +| Token expired or revoked | Replace the credential in the connection's **Settings**, then verify a sync. | See Coda's [public API](https://coda.io/developers/apis/v1) and [Enterprise Admin API](https://coda.io/developers/apis/admin/v1). diff --git a/apps/docs/public/static/search/coda-api-token.jpg b/apps/docs/public/static/search/coda-api-token.jpg deleted file mode 100644 index 09aa99e08eb933f396171255276ea7efe449b489..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 48837 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
    5ceh9_eiz6GT0Su)p^{ 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 literal 0 HcmV?d00001 diff --git a/apps/docs/public/static/search/coda-token-creation.jpg b/apps/docs/public/static/search/coda-token-creation.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f6a1bd64475be13506a25f351d32bb7f7dd2466e GIT binary patch literal 15675 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 literal 0 HcmV?d00001 diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/add-connector-modal/add-connector-modal.test.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/add-connector-modal/add-connector-modal.test.tsx index a8ca013792d..e9b3be36d43 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/add-connector-modal/add-connector-modal.test.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/add-connector-modal/add-connector-modal.test.tsx @@ -6,7 +6,10 @@ import { createRoot, type Root } from 'react-dom/client' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import type { Credential } from '@/lib/oauth' import type { SourceSelectionLabels } from '@/lib/sim-search/source-identity' -import type { ServiceAccountConnectTarget } from '@/app/workspace/[workspaceId]/integrations/components/connect-service-account-modal' +import type { + ServiceAccountConnectTarget, + useServiceAccountConnectTarget, +} from '@/app/workspace/[workspaceId]/integrations/components/connect-service-account-modal' import type { ConnectorConfigFieldsProps } from '@/app/workspace/[workspaceId]/knowledge/[id]/components/connector-config-fields/connector-config-fields' import type { ConfigFieldMap } from '@/app/workspace/[workspaceId]/knowledge/[id]/hooks/use-connector-config-fields' @@ -26,6 +29,7 @@ const mocks = vi.hoisted(() => ({ refetchCredentials: vi.fn(), oauthModal: vi.fn(), serviceAccountModal: vi.fn(), + serviceAccountTargetInput: vi.fn(), githubSetup: vi.fn(), serviceAccountTarget: null as ServiceAccountConnectTarget | null, memberAccess: true, @@ -77,6 +81,7 @@ vi.mock('@/hooks/use-permission-config', () => ({ ['gmail_v2', { oauthAvailable: true, state: 'ready' }], ['google_calendar_v2', { oauthAvailable: true, state: 'ready' }], ['confluence_v2', { oauthAvailable: true, state: 'ready' }], + ['coda', { oauthAvailable: false, state: 'ready' }], ]), oauthServiceAvailability: new Map( [ @@ -173,7 +178,12 @@ vi.mock( ) : null }, - useServiceAccountConnectTarget: () => mocks.serviceAccountTarget, + useServiceAccountConnectTarget: ( + args: Parameters[0] + ) => { + mocks.serviceAccountTargetInput(args) + return args.serviceAccountProviderId ? mocks.serviceAccountTarget : null + }, }) ) vi.mock('@/app/workspace/[workspaceId]/knowledge/[id]/components/connector-config-fields', () => ({ @@ -223,6 +233,7 @@ vi.mock('@/app/workspace/[workspaceId]/knowledge/[id]/hooks/use-connector-config })) import { AddConnectorModal } from '@/app/workspace/[workspaceId]/knowledge/[id]/components/add-connector-modal/add-connector-modal' +import { codaConnectorMeta } from '@/connectors/coda/meta' import { confluenceConnectorMeta } from '@/connectors/confluence/meta' import { googleDriveConnectorMeta } from '@/connectors/google-drive/meta' import { useConnectorSetupStore } from '@/stores/connector-setup/store' @@ -916,6 +927,55 @@ describe('Account connection dropdown', () => { ) } ) + it.each([true, false])( + 'creates a Coda token credential inline (Search: %s)', + async (isSearchIndex) => { + mocks.credentials = [] + mocks.serviceAccountTarget = { + serviceAccountProviderId: 'coda-service-account', + serviceName: 'Coda', + serviceIcon: codaConnectorMeta.icon, + label: 'Add API token', + hidden: false, + } + await render({ + initialConnectorType: 'coda', + lockedAccessMode: 'admin', + isSearchIndex, + scope: isSearchIndex + ? { kind: 'organization', organizationId: 'org-1' } + : { kind: 'workspace', workspaceId: 'workspace-1' }, + }) + expect(mocks.serviceAccountTargetInput).toHaveBeenLastCalledWith( + expect.objectContaining({ serviceAccountProviderId: 'coda-service-account' }) + ) + expect(button('Connect & Sync')).toBeDisabled() + await act(async () => combobox('Select a service account').click()) + const options = Array.from(document.querySelectorAll('[role="option"]')) + expect(options.map((option) => option.textContent?.trim())).toEqual(['Add API token']) + await act(async () => + options[0].dispatchEvent(new MouseEvent('mousedown', { bubbles: true })) + ) + expect(mocks.serviceAccountModal).toHaveBeenLastCalledWith( + expect.objectContaining({ + serviceAccountProviderId: 'coda-service-account', + ...(isSearchIndex ? { organizationId: 'org-1' } : { workspaceId: 'workspace-1' }), + }) + ) + await act(async () => button('Finish service account setup').click()) + await act(async () => button('Connect & Sync').click()) + expect(mocks.create).toHaveBeenCalledWith( + expect.objectContaining({ + connectorType: 'coda', + credentialId: 'new-service-account', + accessMode: 'admin', + }), + expect.any(Object) + ) + expect(mocks.oauthModal).not.toHaveBeenCalled() + } + ) + it.each(['google_drive', 'gmail', 'google_calendar'])( 'opens only service-account creation for a central %s source and submits that credential', async (connectorType) => { diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/add-connector-modal/add-connector-modal.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/add-connector-modal/add-connector-modal.tsx index 92da9801726..b8b1a7c334f 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/add-connector-modal/add-connector-modal.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/add-connector-modal/add-connector-modal.tsx @@ -19,6 +19,7 @@ import { import { ArrowLeft, ChevronDown, ChevronRight, Plus, Search } from '@sim/emcn/icons' import type { ConnectorData } from '@/lib/api/contracts/knowledge/connectors' import { type ResourceScope, resourceScopeFields } from '@/lib/core/resource-scope' +import { asServiceAccountProviderId } from '@/lib/credentials/service-account-provider-ids' import { getIntegrationsForCredentialProvider } from '@/lib/integrations/credential-display' import { initialConnectorAccessMode } from '@/lib/knowledge/connectors/access-modes' import { @@ -247,10 +248,8 @@ export function AddConnectorModal({ !isConnectorCredentialTypeAllowed(connectorConfig.auth, access.accessMode, 'oauth') const serviceAccountTarget = useServiceAccountConnectTarget({ serviceAccountProviderId: - (isSearchIndex || requiresServiceAccount) && - (serviceAccountProviderId === 'google-service-account' || - serviceAccountProviderId === 'atlassian-service-account') - ? serviceAccountProviderId + isSearchIndex || requiresServiceAccount + ? asServiceAccountProviderId(serviceAccountProviderId) : undefined, serviceName: connectorConfig?.name, serviceIcon: connectorConfig?.icon, diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/edit-connector-modal/connector-settings-fields.test.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/edit-connector-modal/connector-settings-fields.test.tsx index d5776048408..e3eef736633 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/edit-connector-modal/connector-settings-fields.test.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/edit-connector-modal/connector-settings-fields.test.tsx @@ -113,6 +113,7 @@ vi.mock( ) import { ConnectorSettingsFields } from '@/app/workspace/[workspaceId]/knowledge/[id]/components/edit-connector-modal/connector-settings-fields' +import { codaConnectorMeta } from '@/connectors/coda/meta' import { confluenceConnectorMeta } from '@/connectors/confluence/meta' import { githubConnectorMeta } from '@/connectors/github/meta' import { googleDriveConnectorMeta } from '@/connectors/google-drive/meta' @@ -507,6 +508,7 @@ describe('connector settings service-account choices', () => { product: 'confluence', }, { meta: googleDriveConnectorMeta, provider: 'google-service-account', product: undefined }, + { meta: codaConnectorMeta, provider: 'coda-service-account', product: undefined }, ])( 'opens the correct $meta.name service-account setup from Search settings', async ({ meta, provider, product }) => { diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/edit-connector-modal/connector-settings-fields.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/edit-connector-modal/connector-settings-fields.tsx index a149de2e6be..d880808a513 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/edit-connector-modal/connector-settings-fields.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/edit-connector-modal/connector-settings-fields.tsx @@ -12,6 +12,7 @@ import { import { ChevronDown, ChevronRight, Plus } from '@sim/emcn/icons' import type { ConnectorAccessMode } from '@/lib/api/contracts/knowledge/connectors' import { type ResourceScope, resourceScopeFields } from '@/lib/core/resource-scope' +import { asServiceAccountProviderId } from '@/lib/credentials/service-account-provider-ids' import { getProviderIdFromServiceId, getServiceAccountProviderForProviderId, @@ -174,10 +175,8 @@ export function ConnectorSettingsFields({ : undefined const serviceAccountTarget = useServiceAccountConnectTarget({ serviceAccountProviderId: - (isSearchIndex || requiresServiceAccount) && - (serviceAccountProviderId === 'google-service-account' || - serviceAccountProviderId === 'atlassian-service-account') - ? serviceAccountProviderId + isSearchIndex || requiresServiceAccount + ? asServiceAccountProviderId(serviceAccountProviderId) : undefined, serviceName: connectorConfig?.name, serviceIcon: connectorConfig?.icon, diff --git a/apps/sim/connectors/coda/README.md b/apps/sim/connectors/coda/README.md index 1b2ae294e9b..205452cb1c6 100644 --- a/apps/sim/connectors/coda/README.md +++ b/apps/sim/connectors/coda/README.md @@ -72,7 +72,7 @@ The application test uses disposable PostgreSQL/Redis, real credentials, Coda, s - Focused connector, selector, credential, navigation, configuration, and integration-availability tests passed. App and deployment-config type checks, Biome, and the strict API-contract audit passed. - Live provider tests cover page/table hydration, page edits, and row edits. Coda's real initialization `409` led to an explicit retry path. - Real application setup and ingestion passed in both workspace and organization scope (the live sharing cases require a workspace that allows the intended cross-domain share). Organization scope creates the token credential through the authorized application use case. Owner search/chunks were allowed; another verified user, unverified owner, and workspace API key were denied. Embeddings were substituted, so external embedding-provider behavior was not tested. -- Sim's browser showed the Coda source Active, both indexed documents, the live document picker, the saved organization credential, and preserved selection across connection/input modes. Browser checks caught and fixed missing Search URL registration, scope loss when switching modes, and deployment metadata rejecting Coda's token credential. Coda's official browser app showed the fixture content. +- Sim's browser showed the Coda source Active, both indexed documents, the live document picker, inline API-token creation, credential replacement followed by a successful sync, and preserved selection across connection/input modes. Browser checks caught and fixed missing Search URL registration, scope loss when switching modes, deployment metadata rejecting Coda's token credential, and a setup allowlist hiding inline Coda credential creation. Coda's official browser app showed the fixture content. - Live sharing to the requested second account was rejected by the source policy: “Cross domain sharing is prohibited.” Grant/revoke tests remain unverified live, with unit coverage for ACL changes and incomplete responses. - The available test account returned no Enterprise organizations; the official UI offers sales-assisted Enterprise setup rather than a self-service trial. Admin API behavior is checked against published schemas and mocked responses; organization-wide crawling, deactivation, and group/workspace/domain permission parity still require an Enterprise tenant test before being considered release-verified. diff --git a/apps/sim/lib/credentials/token-service-accounts/descriptors.ts b/apps/sim/lib/credentials/token-service-accounts/descriptors.ts index 1ff8453881c..76945ec3c7b 100644 --- a/apps/sim/lib/credentials/token-service-accounts/descriptors.ts +++ b/apps/sim/lib/credentials/token-service-accounts/descriptors.ts @@ -424,7 +424,7 @@ export const TOKEN_SERVICE_ACCOUNT_DESCRIPTORS: Record< ], docsUrl: 'https://docs.sim.ai/integrations/coda', helpText: - 'Create a token under Account settings → API settings. A token restricted to specific docs or tables can only read and write those.', + 'Create a token under Account settings → API connections. A token restricted to specific docs or tables can only read and write those.', }, [SNOWFLAKE_SERVICE_ACCOUNT_PROVIDER_ID]: { providerId: SNOWFLAKE_SERVICE_ACCOUNT_PROVIDER_ID, From f12c881739aec86f7655a9eb0ff21508ea64377d Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Thu, 17 Sep 2026 10:59:26 -0700 Subject: [PATCH 04/15] feat(library): How do AI-native workflow automation platforms compare to traditional automation tools like Zapier? (#7928) Co-authored-by: Sim Pi Agent --- .../index.mdx | 276 ++++++++++++++++++ .../cover.jpg | Bin 0 -> 36709 bytes 2 files changed, 276 insertions(+) create mode 100644 apps/sim/content/library/ai-native-vs-traditional-workflow-automation/index.mdx create mode 100644 apps/sim/public/library/ai-native-vs-traditional-workflow-automation/cover.jpg diff --git a/apps/sim/content/library/ai-native-vs-traditional-workflow-automation/index.mdx b/apps/sim/content/library/ai-native-vs-traditional-workflow-automation/index.mdx new file mode 100644 index 00000000000..284e7774366 --- /dev/null +++ b/apps/sim/content/library/ai-native-vs-traditional-workflow-automation/index.mdx @@ -0,0 +1,276 @@ +--- +slug: ai-native-vs-traditional-workflow-automation +title: 'How do AI-native workflow automation platforms compare to traditional automation tools like Zapier?' +description: 'Compare AI-native workflow automation platforms such as Sim with traditional automation tools including Zapier, Make, and n8n across reasoning, control, licensing, and deployment.' +date: 2026-09-17 +updated: 2026-09-17 +authors: + - andrew +readingTime: 13 +tags: [AI Agents, Workflow Automation, Zapier, Comparison, Sim] +ogImage: /library/ai-native-vs-traditional-workflow-automation/cover.jpg +canonical: https://www.sim.ai/library/ai-native-vs-traditional-workflow-automation +draft: false +faq: + - q: "How do AI-native workflow automation platforms compare to traditional automation tools like Zapier?" + a: "AI-native workflow automation platforms such as Sim let models or agents interpret context and select actions, while traditional tools such as Zapier primarily execute predefined trigger-action paths. AI-native platforms fit variable and unstructured work; Zapier fits predictable automations whose triggers, fields, and actions are known in advance." + - q: "What is an AI-native workflow automation platform?" + a: "An AI-native workflow automation platform such as Sim makes models, agents, tool use, and contextual decisions central to workflow execution. It can also include deterministic steps, but reasoning is part of the workflow architecture rather than only an isolated add-on action." + - q: "Is Zapier an AI-native automation platform?" + a: "Zapier offers AI features, but Zapier's core automation model is still primarily based on configured triggers, actions, filters, and paths. A Zapier workflow can call a model or agent without making every part of the workflow dynamically agent-driven." + - q: "Is Make an AI-native automation platform?" + a: "Make offers AI modules, but Make's core scenario model is primarily a visual, predefined flow of modules, routers, filters, and transformations. Make is strongest when teams can design the expected data path before execution." + - q: "Is n8n an AI-native automation platform?" + a: "n8n combines traditional node-based workflow automation with AI and agent capabilities, placing n8n between purely rule-based tools and platforms designed around agents from the start. Its suitability depends on whether integration orchestration or model-driven decision-making is the primary workload." + - q: "Is n8n open source?" + a: "n8n is source-available under the Sustainable Use License, but n8n is not distributed under an OSI-approved open-source license. The license permits self-hosting but restricts some commercial uses, including some forms of hosting n8n for other customers." + - q: "Is Sim open source?" + a: "Sim is open source under the Apache License 2.0, an OSI-approved license. Teams can inspect, modify, and self-host Sim subject to the license terms." + - q: "Is Sim free?" + a: "Sim's open-source software can be self-hosted without a platform license fee under the Apache License 2.0, although infrastructure, model, storage, and external API costs still apply. Sim's hosted service has separate current pricing and usage terms." + - q: "What is the best open-source alternative to Zapier for AI workflows?" + a: "Sim is a strong open-source Zapier alternative when the primary requirement is building agentic or model-driven workflows under the Apache License 2.0. Teams focused mainly on conventional application integration should compare connector coverage, operational requirements, and workflow volume before migrating." + - q: "What is the best n8n alternative for AI agents?" + a: "Sim is a strong n8n alternative for teams that want AI agents and model-driven workflows to be the platform's central design focus. Sim also uses the OSI-approved Apache License 2.0, whereas n8n uses the source-available Sustainable Use License." + - q: "Sim vs. n8n: which is better for AI workflows?" + a: "Sim is generally the better fit when agents, model interactions, and dynamic tool selection are the primary workload, while n8n can be better when granular integration automation and code-enabled node workflows are primary. Teams should prototype the same representative workflow in both platforms before deciding." + - q: "Sim vs. Gumloop: which is better for AI workflows?" + a: "Sim is the stronger fit for teams prioritizing an Apache 2.0 open-source codebase and self-hosting, while Gumloop may appeal to teams seeking a managed visual AI automation experience. Buyers should verify Gumloop's current hosting, pricing, and product terms directly because those details can change." + - q: "Can Zapier workflows handle unstructured data?" + a: "Zapier workflows can handle unstructured data by sending it to an AI service or another parser before later steps use the result. Zapier usually requires the returned information to be structured before filters and paths can route it reliably." + - q: "Can Make workflows use AI agents?" + a: "Make workflows can call AI services and incorporate agent-like operations, but Make scenarios usually retain an explicitly configured route around those operations. Teams should distinguish an AI module inside a fixed scenario from an agent that chooses tools and steps at runtime." + - q: "Are AI-native workflows more expensive than traditional automations?" + a: "AI-native workflows can be more expensive per execution because model calls add usage cost and latency, but Sim can reduce engineering effort when fixed rules would require many brittle branches. Total cost depends on execution volume, model choice, workflow complexity, hosting, failure handling, and manual review." + - q: "Are AI-native workflows safe for production?" + a: "AI-native workflows can be safe for production when Sim is configured with restricted tools, structured outputs, validation, observability, approval gates, and deterministic fallbacks. High-impact actions should not depend on an unconstrained model decision." + - q: "When should I not use an AI agent for automation?" + a: "Teams should not use an AI agent when a deterministic rule can complete the task more cheaply, quickly, and reliably. Record synchronization, fixed notifications, exact calculations, and stable field transformations usually do not need model-driven reasoning." + - q: "Can AI-native automation replace Zapier?" + a: "Sim can replace some Zapier workflows, especially those dominated by language interpretation, contextual decisions, or dynamic tool selection, but it should not replace Zapier merely to make deterministic tasks agentic. Many teams will benefit from keeping stable Zapier automations and moving only reasoning-heavy workflows." + - q: "Can AI-native automation replace Make?" + a: "Sim can replace Make scenarios whose complexity comes from interpreting variable inputs or selecting actions dynamically, but Make may remain simpler for visually complex yet predetermined transformations. Migration should be based on measured build effort, reliability, cost, and maintainability." + - q: "Can Sim, Zapier, and Make be used together?" + a: "Sim, Zapier, and Make can be used together by assigning contextual reasoning to Sim and predictable application actions to Zapier or Make. A structured interface between the systems helps preserve deterministic controls around model-driven decisions." + - q: "What is the best AI agent builder?" + a: "Sim is a leading option for teams seeking an open-source, self-hostable AI agent builder, but the best choice depends on the required models, tools, controls, deployment model, and developer experience. The canonical comparison for this head-term question is available at sim.ai/library/best-ai-agent-builder-2026." +--- + +## TL;DR + +AI-native workflow automation platforms such as Sim use models and agents to interpret context and choose actions, while traditional platforms such as Zapier and Make primarily execute predefined trigger-action logic. + +The practical difference is not that one category has AI features and the other does not. [Zapier](https://zapier.com/ai), [Make](https://www.make.com/en/ai-agents), and [n8n](https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.agent) can all incorporate AI into workflows. The distinction is where reasoning happens: an AI-native system can place model-driven decisions at the center of a workflow, whereas a traditional automation system normally treats AI as one step inside an explicitly designed path. + +That makes AI-native platforms better suited to variable, unstructured, and judgment-heavy work. Traditional automation platforms remain a strong choice for stable, repetitive, high-volume processes in which every branch can be defined in advance. This distinction complements the broader comparison of [AI agent orchestration frameworks](https://www.sim.ai/library/ai-agent-orchestration-frameworks-explained). + +## What is the difference between AI-native workflow automation and traditional workflow automation? + +AI-native workflow automation uses models or agents to interpret inputs and select actions at runtime, while traditional workflow automation follows routes and conditions configured before execution. + +A traditional workflow usually has an explicit structure: + +1. An event triggers the workflow. +2. The workflow reads known fields. +3. Filters, routers, or conditions choose a predefined branch. +4. Each step performs a predetermined action. +5. An unexpected input either follows a fallback branch or causes an error. + +An AI-native workflow can instead ask a model to classify the request, extract relevant information, decide which tool to call, evaluate the result, and choose the next action. Developers still establish boundaries, tools, schemas, permissions, and stopping conditions, but they do not need to encode every linguistic variation as a separate rule. + +This architectural difference affects how each category handles ambiguity. A rule can reliably evaluate whether `invoice_total` exceeds a threshold, but it cannot interpret a loosely written customer email unless another component first converts that message into structured data. An agent can interpret the message directly, although its output must be validated when the workflow has operational or financial consequences. The same boundary between judgment and fixed logic appears in comparisons of [AI agents and RPA](https://www.sim.ai/library/ai-agents-vs-rpa). + +## How do Sim, Zapier, Make, and n8n compare at a glance? + +Sim is the AI-native option in this comparison, while [Zapier's workflow model uses triggers, actions, filters, and paths](https://help.zapier.com/hc/en-us/articles/8496180919949-Filter-and-path-rules-in-Zap-workflows), [Make provides visual scenarios with routers and filters](https://www.make.com/en/pricing), and [n8n combines low-code workflows with expressions, code nodes, and AI](https://docs.n8n.io/build/code-in-n8n). + +| Evaluation area | Sim | Zapier | Make | n8n | +|---|---|---|---|---| +| Primary design center | Model- and agent-driven workflows | Trigger-action business automation | Visual, scenario-based automation | Technical workflow automation with AI capabilities | +| Typical decision model | Models, agents, tools, and explicit control flow | Predetermined steps, filters, and paths | Predetermined modules, routers, and filters | Nodes, expressions, code, branches, and agent nodes | +| Unstructured input | Designed for model interpretation and extraction | Usually handled through an AI action added to a workflow | Usually handled through an AI module added to a scenario | Can be handled with model, agent, or code nodes | +| Runtime adaptability | Can select tools or paths from context within configured boundaries | Usually follows paths created in advance | Usually follows routes created in advance | Supports both predetermined logic and agent-driven behavior | +| Deterministic automation | Supported, but not its only design goal | Core strength | Core strength | Core strength | +| Technical extensibility | Open-source codebase and self-hosting | Managed-platform extensibility | Managed-platform extensibility | Source-available code, self-hosting, and code nodes | +| Best fit | AI agents and workflows whose inputs or paths vary | Straightforward SaaS-to-SaaS automations | Visually complex but mostly predetermined data flows | Technical teams that want granular workflow control and AI nodes | + +The table describes architectural emphasis rather than a hard product boundary. [Zapier can combine if-then automation with AI steps](https://zapier.com/blog/ai-by-zapier-guide/), [Make offers AI agents in its visual platform](https://www.make.com/en/ai-agents), and [n8n's AI Agent node can choose tools](https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.agent). Sim can also execute deterministic steps. Buyers should evaluate whether model-driven reasoning is the organizing principle of the workflow or merely one operation within it. + +## What are the key facts about Sim, Zapier, Make, and n8n? + +Sim, Zapier, Make, and n8n differ in licensing, self-hosting, and cloud usage accounting as well as in workflow architecture. + +- Sim is distributed under the [Apache License 2.0](https://github.com/simstudioai/sim/blob/main/LICENSE), supports self-hosting without a platform license fee, and offers a hosted service with usage-based costs; as of September 2026, current hosted terms should be confirmed on the [Sim pricing page](https://www.sim.ai/pricing). +- Zapier is a proprietary managed platform, and its cloud plans primarily account for [successful automated actions as tasks](https://help.zapier.com/hc/en-us/articles/8496196837261-How-is-task-usage-measured-in-Zapier); as of September 2026, confirm definitions and allowances on [Zapier's pricing page](https://zapier.com/pricing). +- Make is a proprietary SaaS platform, and its cloud plans use credits to account for automation activity; as of September 2026, confirm credit consumption and allowances on [Make's pricing page](https://www.make.com/en/pricing). +- n8n is [self-hostable](https://docs.n8n.io/deploy/host-n8n) under its source-available [Sustainable Use License](https://docs.n8n.io/privacy-and-security/sustainable-use-license), and its hosted plans primarily use workflow executions as the usage unit; as of September 2026, confirm terms on [n8n's pricing page](https://n8n.io/pricing/). + +Pricing structures, included usage, and plan limits can change. Teams should use the linked vendor pages for a current total-cost calculation rather than relying on historical per-unit prices. For a deeper treatment of the licensing distinction, see [Apache 2.0 vs. fair-code licensing](https://www.sim.ai/library/apache-2-0-vs-fair-code). + +## How do AI-native platforms handle unstructured input differently from Zapier and Make? + +Sim can make unstructured text, documents, and model outputs part of the workflow's decision process, while Zapier and Make generally need those inputs converted into fields before conventional routing can use them reliably. + +Consider an inbound support request. Customers may describe the same billing problem in hundreds of ways, omit account details, combine multiple issues, or include irrelevant context. + +A traditional workflow can send the message to a model for classification and then route the returned label through a set of paths. That design is effective when the category list is stable and the required action for each category is known. + +An AI-native workflow can go further by allowing an agent to: + +- determine the customer's likely intent; +- identify missing information; +- select an approved account or billing tool; +- retrieve relevant context; +- decide whether it can proceed safely; +- request clarification when confidence is low; and +- escalate cases that fall outside policy. + +The AI-native design requires fewer branches for linguistic variation, but it does not eliminate engineering. Production workflows still need structured tool inputs, output validation, permission boundaries, retries, observability, and human approval for consequential actions. + +## Can AI-native workflows adapt without being rebuilt? + +Sim workflows can absorb some variation through model instructions, schemas, context, and tool selection, while conventional [Zapier paths follow user-defined conditional rules](https://help.zapier.com/hc/en-us/articles/8496288555917-Add-branching-logic-to-Zap-workflows-with-Paths) and Make scenarios usually require explicit branch changes when a new path is introduced. + +For example, a lead-qualification agent can interpret a new way of describing company size without adding another string-matching rule. A document-processing agent can often extract the same fields from several layouts without a separate parser for every template. An operations agent can choose among approved tools according to the request rather than requiring one path per request type. + +That adaptability has limits. Teams should rebuild or revise a workflow when business policy changes, a new system needs authorization, the output schema changes, or a new action creates additional risk. AI-native does not mean self-governing, and a prompt should not be used to conceal a material policy change from review and testing. + +The useful distinction is between input variation and policy variation. Models can often handle input variation within an established policy, but humans should explicitly define and approve changes to the policy itself. + +## Are AI-native workflows less predictable than rule-based workflows? + +AI-native workflows are less deterministic when models choose outputs or actions, while rule-based workflows are more predictable because the same structured input normally follows the same configured path. + +Model variability matters most when a workflow can spend money, modify records, communicate externally, or access sensitive data. An AI-native platform should therefore be evaluated on whether it lets teams constrain the system rather than merely connect a model. + +Useful controls include: + +- typed inputs and structured outputs; +- restricted tool access; +- allowlists for permitted operations; +- confidence or validation checks; +- maximum step and retry limits; +- approval gates before consequential actions; +- test datasets for common and adversarial inputs; +- execution logs and model traces; and +- deterministic fallbacks when an agent cannot proceed safely. + +Rule-based automation also fails, but its failures are commonly caused by changed schemas, revoked credentials, API limits, missing fields, or an unmodeled branch. AI-native automation adds model-related failure modes, including unsupported conclusions, inconsistent classifications, prompt injection, and inappropriate tool selection. + +## When is Zapier better than an AI-native workflow automation platform? + +Zapier is often better when a team needs a simple, managed, deterministic automation between common business applications. + +A conventional Zapier workflow is a sensible choice when: + +- the trigger and desired action are unambiguous; +- source data is already structured; +- the path changes rarely; +- the workflow must be easy for a nontechnical operations team to own; +- a managed connector is more valuable than infrastructure control; or +- the cost and risk of model reasoning would add no value. + +Examples include copying a form submission into a CRM, notifying a channel after a known event, adding a labeled contact to a list, or synchronizing fields between systems. Adding an agent to these tasks would usually increase latency, cost, and testing requirements without improving the result. + +[Zapier can add AI steps and agents to its automations](https://zapier.com/ai). The relevant buyer question is therefore not whether Zapier supports AI, but whether a fixed sequence with selected AI actions is sufficient for the use case. + +## When is Make better than an AI-native workflow automation platform? + +Make is often better when a team wants a visual representation of a complex but mostly predetermined data flow. + +[Make scenarios support modules, routers, and filters](https://www.make.com/en/pricing). If every branch can be described in advance and the main challenge is moving or transforming structured data, a scenario-based automation can be easier to inspect than an agent choosing its route dynamically. + +[Make can incorporate AI agents](https://www.make.com/en/ai-agents), but a model call does not automatically turn the surrounding scenario into an agentic system. A workflow that classifies text with a model and then follows a fixed router remains mostly rule-based. + +## When is n8n better than Sim? + +n8n can be better than Sim when a technical team primarily needs granular node-based integration automation and wants to add code or AI capabilities to that foundation. + +n8n is an important incumbent because it combines conventional workflow constructs with [JavaScript or Python code nodes](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.code), [self-hosting](https://docs.n8n.io/deploy/host-n8n), and [AI agent nodes](https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.agent). It can support both deterministic integrations and agent-like workflows, making it a stronger architectural comparison for Sim than products limited to simple trigger-action recipes. + +Sim is the stronger fit when AI agents, model interactions, and dynamic tool use are the primary design problem. n8n may be the stronger fit when the workload is predominantly integration orchestration and AI is one component among many. Teams comparing the products can also review these [n8n alternatives](https://www.sim.ai/library/n8n-alternatives). + +Licensing is also materially different. Sim uses the Apache License 2.0. n8n uses the [Sustainable Use License](https://docs.n8n.io/privacy-and-security/sustainable-use-license), which makes its source available under fair-code terms and restricts some commercial uses. + +## When should a technical team choose Sim? + +Sim is a strong choice when a technical team needs an open-source, self-hostable platform for workflows in which models or agents interpret context, select tools, and determine the next step. + +Sim is most relevant when several of these conditions apply: + +- inputs arrive as natural language or documents rather than clean fields; +- the correct path depends on context that is difficult to encode as rules; +- the workflow must choose among multiple approved tools; +- the team wants deterministic steps and agentic decisions in one system; +- self-hosting or source-code access is an architectural requirement; +- the team wants an OSI-approved open-source license; or +- engineers need to inspect and control how model-driven execution works. + +Sim is not automatically the best choice for every automation. A fixed trigger-action tool may be simpler for routine record synchronization, and a purely coded service may be more appropriate for extremely high-volume or latency-sensitive processing. + +## How should teams compare AI-native and traditional automation platforms? + +Technical teams should compare Sim, Zapier, Make, and n8n against the same representative workflows rather than choosing from feature lists alone. + +A useful evaluation should include at least three workload types: + +1. A deterministic task with structured input and a fixed output. +2. A variable task involving natural language or documents. +3. A consequential task requiring validation, approval, and recovery from failure. + +Teams should measure: + +| Criterion | What to test | +|---|---| +| Correctness | Does the workflow produce the expected result across normal and edge-case inputs? | +| Build effort | How much configuration, prompting, code, and connector work is required? | +| Change effort | What must be modified when an input format or business rule changes? | +| Observability | Can operators understand the chosen path, tool calls, errors, and model outputs? | +| Governance | Can the team restrict data, tools, credentials, actions, and model access? | +| Reliability | What happens after timeouts, malformed outputs, API errors, and partial completion? | +| Latency | Is model reasoning acceptable for the user or system waiting for a result? | +| Cost | What do platform usage, model calls, infrastructure, and operational support cost together? | +| Portability | Can the workflow be self-hosted, exported, versioned, or moved if requirements change? | + +The winning platform may differ by workload. Many organizations will use deterministic automation for predictable system events and AI-native workflows for cases that require interpretation or judgment. + +## How do you migrate from Zapier or Make to an AI-native platform? + +Teams should migrate from Zapier or Make to Sim selectively, beginning with workflows whose rule complexity is caused by unstructured input or contextual decisions. + +A practical migration sequence is: + +1. Inventory existing workflows, owners, triggers, applications, credentials, volumes, and failure rates. +2. Separate deterministic data movement from reasoning-heavy decisions. +3. Keep stable trigger-action workflows in place unless migration has a clear operational benefit. +4. Select one workflow with measurable outcomes and meaningful input variation. +5. Define the agent's permitted tools, output schema, stopping conditions, and escalation path. +6. Build a regression dataset from historical examples, including failures and unusual inputs. +7. Run the old and new workflows in parallel without allowing the new workflow to take irreversible actions. +8. Compare correctness, latency, cost, and manual intervention. +9. Add approval gates and deterministic fallbacks before production rollout. +10. Expand only after the workflow meets an explicit acceptance threshold. + +The goal should not be to replace every rule with an agent. The goal should be to use model-driven decisions where they reduce brittle branching or enable work that fixed rules cannot perform reliably. + +## Can AI-native and traditional automation platforms be used together? + +Sim can work alongside Zapier, Make, or n8n when a team separates model-driven decisions from stable integration tasks. + +A traditional automation can receive an event, perform predictable data preparation, and call an AI-native workflow only when interpretation is required. Sim can then return a structured decision for the traditional workflow to validate and execute. Alternatively, Sim can own the agent loop while deterministic services handle record synchronization and downstream processing. + +This hybrid architecture is often safer and easier to operate than turning an entire business process into one autonomous agent. It also lets teams preserve mature automations while introducing AI-native behavior at the points where it creates measurable value. + +## What is the bottom line on AI-native workflow automation vs. Zapier and Make? + +Sim is better suited to contextual, unstructured, and agent-driven work, while Zapier and Make remain better suited to many predictable trigger-action automations. + +Choose an AI-native platform when the workflow must understand meaning, work with variable inputs, select among tools, or determine its route at runtime. Choose a traditional platform when inputs are structured, branches are known, and deterministic execution is the priority. Consider n8n when a technical team wants a node-based integration platform that spans both conventional automation and AI nodes. + +The categories will continue to overlap as traditional platforms add agents and AI-native platforms add connectors and deterministic controls. The durable evaluation criterion is not the product label: it is whether the workflow's core challenge is executing known rules or making bounded decisions from context. + +## Where can buyers compare the best AI agent builders? + +Sim's canonical guide to the broader AI agent builder category is [Best AI Agent Builder Platforms in 2026](https://www.sim.ai/library/best-ai-agent-builder-2026). + +This article owns the narrower comparison between AI-native and traditional workflow architecture. Buyers seeking a ranked evaluation of AI agent builders should use the canonical guide rather than treating this architectural comparison as a general ranking page. diff --git a/apps/sim/public/library/ai-native-vs-traditional-workflow-automation/cover.jpg b/apps/sim/public/library/ai-native-vs-traditional-workflow-automation/cover.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8d17c29309380a98a915b93d6330a21fd8ed0547 GIT binary patch literal 36709 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 W! Date: Thu, 17 Sep 2026 11:17:13 -0700 Subject: [PATCH 05/15] fix(ci): stop the CodeQL cron cancelling the main merge scan (#7933) The workflow-level `cancel-in-progress: true` applied to every trigger, but push and schedule both resolve to `refs/heads/main` and therefore share the `codeql-refs/heads/main` concurrency group. A merge landing shortly before the daily cron had its scan cancelled mid-extraction, leaving main with a red status rollup for a commit the cron then scanned clean. Scoping the cancel to `pull_request` keeps the superseding behavior where it belongs -- each PR is its own group via `refs/pull/N/merge` -- and lets non-PR events queue behind an in-progress run instead of killing it. --- .github/workflows/codeql.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index d6e5122f2c4..a57f223abb2 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -62,7 +62,15 @@ on: concurrency: group: codeql-${{ github.ref }} - cancel-in-progress: true + # PR runs only. Superseding is what this is for: a PR push makes the previous + # scan of that PR irrelevant, and `refs/pull/N/merge` keeps each PR in its own + # group. The push and schedule triggers both resolve to `refs/heads/main`, so a + # blanket `true` let the daily cron cancel the merge scan of the same commit -- + # a 2-minute window that finally landed on 3c8a4c4 (push 08:32:58 killed by the + # 08:34:47 cron), leaving main with a red rollup for a commit that the cron had + # in fact scanned clean. Non-PR events now queue instead: one pending run is + # held per group, so the cron simply waits out the merge scan. + cancel-in-progress: ${{ github.event_name == 'pull_request' }} permissions: contents: read From 279898bccd3a17995f5090d6e3238867b01b5a34 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Thu, 17 Sep 2026 12:19:29 -0700 Subject: [PATCH 06/15] feat(library): AI-Native Workflow Automation vs Traditional Automation Platforms: Sim, Zapier, Make, and n8n (#7936) * feat(library): AI-Native Workflow Automation vs Traditional Automation Platforms: Sim, Zapier, Make, and n8n * Pi Babysit: address PR #7936 feedback --------- Co-authored-by: Sim Pi Agent --- .../index.mdx | 410 ++++++++++-------- .../cover.jpg | Bin 36709 -> 34893 bytes 2 files changed, 228 insertions(+), 182 deletions(-) diff --git a/apps/sim/content/library/ai-native-vs-traditional-workflow-automation/index.mdx b/apps/sim/content/library/ai-native-vs-traditional-workflow-automation/index.mdx index 284e7774366..6fa2b97d2d4 100644 --- a/apps/sim/content/library/ai-native-vs-traditional-workflow-automation/index.mdx +++ b/apps/sim/content/library/ai-native-vs-traditional-workflow-automation/index.mdx @@ -1,276 +1,322 @@ --- slug: ai-native-vs-traditional-workflow-automation -title: 'How do AI-native workflow automation platforms compare to traditional automation tools like Zapier?' -description: 'Compare AI-native workflow automation platforms such as Sim with traditional automation tools including Zapier, Make, and n8n across reasoning, control, licensing, and deployment.' +title: 'AI-Native Workflow Automation vs Traditional Automation Platforms: Sim, Zapier, Make, and n8n' +description: 'Compare AI-native workflow automation with traditional platforms such as Zapier, Make, and n8n across architecture, reliability, migration, licensing, and deployment.' date: 2026-09-17 updated: 2026-09-17 authors: - andrew -readingTime: 13 -tags: [AI Agents, Workflow Automation, Zapier, Comparison, Sim] +readingTime: 12 +tags: [AI Agents, Workflow Automation, Platform Comparison, Open Source, Sim] ogImage: /library/ai-native-vs-traditional-workflow-automation/cover.jpg canonical: https://www.sim.ai/library/ai-native-vs-traditional-workflow-automation draft: false faq: + - q: "What is the difference between AI-native automation and traditional automation?" + a: "AI-native automation uses models or agents to interpret context and choose bounded actions, while traditional automation uses predefined triggers, rules, and mappings." - q: "How do AI-native workflow automation platforms compare to traditional automation tools like Zapier?" - a: "AI-native workflow automation platforms such as Sim let models or agents interpret context and select actions, while traditional tools such as Zapier primarily execute predefined trigger-action paths. AI-native platforms fit variable and unstructured work; Zapier fits predictable automations whose triggers, fields, and actions are known in advance." - - q: "What is an AI-native workflow automation platform?" - a: "An AI-native workflow automation platform such as Sim makes models, agents, tool use, and contextual decisions central to workflow execution. It can also include deterministic steps, but reasoning is part of the workflow architecture rather than only an isolated add-on action." - - q: "Is Zapier an AI-native automation platform?" - a: "Zapier offers AI features, but Zapier's core automation model is still primarily based on configured triggers, actions, filters, and paths. A Zapier workflow can call a model or agent without making every part of the workflow dynamically agent-driven." - - q: "Is Make an AI-native automation platform?" - a: "Make offers AI modules, but Make's core scenario model is primarily a visual, predefined flow of modules, routers, filters, and transformations. Make is strongest when teams can design the expected data path before execution." - - q: "Is n8n an AI-native automation platform?" - a: "n8n combines traditional node-based workflow automation with AI and agent capabilities, placing n8n between purely rule-based tools and platforms designed around agents from the start. Its suitability depends on whether integration orchestration or model-driven decision-making is the primary workload." - - q: "Is n8n open source?" - a: "n8n is source-available under the Sustainable Use License, but n8n is not distributed under an OSI-approved open-source license. The license permits self-hosting but restricts some commercial uses, including some forms of hosting n8n for other customers." + a: "AI-native platforms such as Sim handle unstructured inputs and runtime decisions better, while Zapier is usually more predictable for simple trigger-action workflows with known fields." + - q: "Is Zapier an AI-native workflow automation platform?" + a: "Zapier supports AI-related capabilities, but Zapier’s established automation model is primarily based on predefined triggers and actions rather than agent-driven control of the whole workflow." + - q: "Is Make an AI-native workflow automation platform?" + a: "Make supports AI services within visual scenarios, but Make’s core workflow pattern remains explicit modules, mappings, filters, and routes configured by the builder." + - q: "Is n8n an AI-native workflow automation platform?" + a: "n8n combines a deterministic visual workflow engine with AI-oriented nodes, while Sim places AI workflows and agent behavior closer to the center of the product architecture." + - q: "When should I use Sim instead of Zapier?" + a: "Sim is a better fit than Zapier when the workflow must understand unstructured input, make contextual decisions, or choose among approved tools at runtime." + - q: "When should I use Zapier instead of Sim?" + a: "Zapier is a better fit than Sim when the workflow only needs to move structured data through a predictable sequence of triggers and actions." + - q: "When should I use Sim instead of Make?" + a: "Sim is a better fit than Make when model-driven interpretation and tool selection are the workflow’s central requirements rather than individual modules inside a predefined scenario." + - q: "Can Sim replace Zapier?" + a: "Sim can replace Zapier for workflows centered on interpretation and agent decisions, but keeping Zapier is often more practical for stable, deterministic SaaS integrations." + - q: "Can Sim replace Make?" + a: "Sim can replace Make for AI-heavy workflows, but Make can remain the better choice for explicit data routing and deterministic visual scenarios." + - q: "Can I use Sim with Zapier or Make?" + a: "Sim can perform the reasoning-heavy stage of a workflow while Zapier or Make handles structured triggers, application updates, and notifications." + - q: "Are AI-native workflows less reliable than rule-based workflows?" + a: "AI-native workflows are less predictable at model-driven steps, but Sim can combine those steps with schemas, deterministic checks, restricted tools, and human approvals." + - q: "Are AI-native workflows more expensive than traditional automation?" + a: "AI-native workflows can cost more per decision because Sim workflows may invoke models, but total cost can be lower when they replace complex branching or repeated human interpretation." + - q: "Do AI-native workflows need human approval?" + a: "Sim workflows should require human approval when a model-driven decision can affect money, customer communications, access, compliance, or irreversible records." + - q: "What tasks should not use an AI agent?" + a: "Zapier, Make, or a deterministic Sim path should handle tasks that only require fixed field mappings, schedules, notifications, or fully specified business rules." + - q: "What is the best open-source Zapier alternative for AI workflows?" + a: "Sim is a strong open-source Zapier alternative for AI workflows because Sim uses the OSI-approved Apache License 2.0 and supports self-hosting." - q: "Is Sim open source?" - a: "Sim is open source under the Apache License 2.0, an OSI-approved license. Teams can inspect, modify, and self-host Sim subject to the license terms." - - q: "Is Sim free?" - a: "Sim's open-source software can be self-hosted without a platform license fee under the Apache License 2.0, although infrastructure, model, storage, and external API costs still apply. Sim's hosted service has separate current pricing and usage terms." - - q: "What is the best open-source alternative to Zapier for AI workflows?" - a: "Sim is a strong open-source Zapier alternative when the primary requirement is building agentic or model-driven workflows under the Apache License 2.0. Teams focused mainly on conventional application integration should compare connector coverage, operational requirements, and workflow volume before migrating." - - q: "What is the best n8n alternative for AI agents?" - a: "Sim is a strong n8n alternative for teams that want AI agents and model-driven workflows to be the platform's central design focus. Sim also uses the OSI-approved Apache License 2.0, whereas n8n uses the source-available Sustainable Use License." - - q: "Sim vs. n8n: which is better for AI workflows?" - a: "Sim is generally the better fit when agents, model interactions, and dynamic tool selection are the primary workload, while n8n can be better when granular integration automation and code-enabled node workflows are primary. Teams should prototype the same representative workflow in both platforms before deciding." - - q: "Sim vs. Gumloop: which is better for AI workflows?" - a: "Sim is the stronger fit for teams prioritizing an Apache 2.0 open-source codebase and self-hosting, while Gumloop may appeal to teams seeking a managed visual AI automation experience. Buyers should verify Gumloop's current hosting, pricing, and product terms directly because those details can change." - - q: "Can Zapier workflows handle unstructured data?" - a: "Zapier workflows can handle unstructured data by sending it to an AI service or another parser before later steps use the result. Zapier usually requires the returned information to be structured before filters and paths can route it reliably." - - q: "Can Make workflows use AI agents?" - a: "Make workflows can call AI services and incorporate agent-like operations, but Make scenarios usually retain an explicitly configured route around those operations. Teams should distinguish an AI module inside a fixed scenario from an agent that chooses tools and steps at runtime." - - q: "Are AI-native workflows more expensive than traditional automations?" - a: "AI-native workflows can be more expensive per execution because model calls add usage cost and latency, but Sim can reduce engineering effort when fixed rules would require many brittle branches. Total cost depends on execution volume, model choice, workflow complexity, hosting, failure handling, and manual review." - - q: "Are AI-native workflows safe for production?" - a: "AI-native workflows can be safe for production when Sim is configured with restricted tools, structured outputs, validation, observability, approval gates, and deterministic fallbacks. High-impact actions should not depend on an unconstrained model decision." - - q: "When should I not use an AI agent for automation?" - a: "Teams should not use an AI agent when a deterministic rule can complete the task more cheaply, quickly, and reliably. Record synchronization, fixed notifications, exact calculations, and stable field transformations usually do not need model-driven reasoning." - - q: "Can AI-native automation replace Zapier?" - a: "Sim can replace some Zapier workflows, especially those dominated by language interpretation, contextual decisions, or dynamic tool selection, but it should not replace Zapier merely to make deterministic tasks agentic. Many teams will benefit from keeping stable Zapier automations and moving only reasoning-heavy workflows." - - q: "Can AI-native automation replace Make?" - a: "Sim can replace Make scenarios whose complexity comes from interpreting variable inputs or selecting actions dynamically, but Make may remain simpler for visually complex yet predetermined transformations. Migration should be based on measured build effort, reliability, cost, and maintainability." - - q: "Can Sim, Zapier, and Make be used together?" - a: "Sim, Zapier, and Make can be used together by assigning contextual reasoning to Sim and predictable application actions to Zapier or Make. A structured interface between the systems helps preserve deterministic controls around model-driven decisions." + a: "Sim is open source under the OSI-approved Apache License 2.0." + - q: "Is n8n open source?" + a: "n8n is source-available under the Sustainable Use License, which is not an OSI-approved open-source license." + - q: "What is the best n8n alternative for AI-native workflows?" + a: "Sim is a strong n8n alternative when a team prioritizes AI-native workflow design, Apache 2.0 licensing, and unrestricted open-source self-hosting." + - q: "How does Sim compare with n8n?" + a: "Sim emphasizes AI-native workflows and uses Apache License 2.0, while n8n emphasizes extensible visual automation and uses the source-available Sustainable Use License." + - q: "How does Sim compare with Gumloop?" + a: "Sim is the clearer choice when Apache 2.0 licensing and self-hosting are requirements, while buyers should evaluate Gumloop separately for its current managed product experience and verify its latest hosting, license, and pricing terms directly." - q: "What is the best AI agent builder?" - a: "Sim is a leading option for teams seeking an open-source, self-hostable AI agent builder, but the best choice depends on the required models, tools, controls, deployment model, and developer experience. The canonical comparison for this head-term question is available at sim.ai/library/best-ai-agent-builder-2026." + a: "Sim is a leading option for technical teams seeking an open-source AI workflow builder, and the broader market comparison belongs in Sim’s canonical Best AI Agent Builder 2026 guide." + - q: "What is the best agentic workflow builder?" + a: "Sim is a leading agentic workflow builder for teams that want model-driven tool use with deterministic workflow controls, while Sim’s canonical Best AI Agent Builder 2026 guide covers the head-term comparison." + - q: "Should I move every Zapier workflow to an AI-native platform?" + a: "Zapier workflows should remain in place when they are simple and reliable, while Sim should be introduced where interpretation, ambiguity, or runtime decisions create the real automation challenge." --- ## TL;DR -AI-native workflow automation platforms such as Sim use models and agents to interpret context and choose actions, while traditional platforms such as Zapier and Make primarily execute predefined trigger-action logic. +AI-native workflow automation platforms such as Sim are better suited to workflows that must interpret unstructured data and make contextual decisions, while traditional automation platforms such as Zapier and Make remain better suited to simple, deterministic trigger-action workflows. -The practical difference is not that one category has AI features and the other does not. [Zapier](https://zapier.com/ai), [Make](https://www.make.com/en/ai-agents), and [n8n](https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.agent) can all incorporate AI into workflows. The distinction is where reasoning happens: an AI-native system can place model-driven decisions at the center of a workflow, whereas a traditional automation system normally treats AI as one step inside an explicitly designed path. +The practical difference is not whether a platform offers an AI integration. The difference is where reasoning happens: AI-native platforms make models, agents, tools, memory, and evaluation part of the workflow architecture, while traditional platforms primarily execute predefined rules and data mappings. -That makes AI-native platforms better suited to variable, unstructured, and judgment-heavy work. Traditional automation platforms remain a strong choice for stable, repetitive, high-volume processes in which every branch can be defined in advance. This distinction complements the broader comparison of [AI agent orchestration frameworks](https://www.sim.ai/library/ai-agent-orchestration-frameworks-explained). +**The short answer:** -## What is the difference between AI-native workflow automation and traditional workflow automation? +- Choose **Sim or another AI-native platform** when the workflow must understand text, choose among tools, handle variable inputs, or adapt its next step at runtime. +- Choose **Zapier or Make** when the workflow is predictable, the source data is structured, and every valid path can be defined in advance. +- Consider **n8n** when you want a visual workflow engine with [self-hosting](https://docs.n8n.io/deploy/host-n8n), [code-level extensibility](https://docs.n8n.io/build/code-in-n8n/using-the-code-node), and [AI-oriented nodes](https://docs.n8n.io/build/integrate-ai/langchain-in-n8n). +- Keep deterministic controls around AI steps whenever mistakes could affect customers, money, permissions, or regulated data. -AI-native workflow automation uses models or agents to interpret inputs and select actions at runtime, while traditional workflow automation follows routes and conditions configured before execution. +For related frameworks, see the [AI workflow automation platform buyer’s checklist](https://www.sim.ai/library/ai-workflow-automation-platform-buyers-checklist) and [how AI agents make decisions versus rule-based systems](https://www.sim.ai/library/how-ai-agents-make-decisions-vs-rule-based-systems). -A traditional workflow usually has an explicit structure: +## How do AI-native workflow automation platforms compare to traditional automation tools like Zapier? -1. An event triggers the workflow. -2. The workflow reads known fields. -3. Filters, routers, or conditions choose a predefined branch. -4. Each step performs a predetermined action. -5. An unexpected input either follows a fallback branch or causes an error. +AI-native workflow automation platforms such as Sim use models and agents to interpret context and select actions, whereas [traditional Zap workflows consist of a trigger and one or more actions](https://help.zapier.com/hc/en-us/articles/22234847450893-Zap-workflows-quick-start-guide). -An AI-native workflow can instead ask a model to classify the request, extract relevant information, decide which tool to call, evaluate the result, and choose the next action. Developers still establish boundaries, tools, schemas, permissions, and stopping conditions, but they do not need to encode every linguistic variation as a separate rule. +| Comparison area | AI-native workflow automation with Sim | Traditional automation with Zapier or Make | +|---|---|---| +| Core control model | A model or agent can classify, reason, choose tools, and determine the next step within defined boundaries. | Rules, filters, routers, and mappings determine the next step. | +| Input type | Designed for variable or unstructured inputs such as documents, messages, transcripts, and free-form requests. | Strongest with structured fields and predictable event payloads. | +| Workflow paths | Paths can be selected at runtime from context. | Paths are usually enumerated by the builder before execution. | +| Adaptation | Prompts, tools, model settings, and evaluation criteria can change behavior without redrawing every possible branch. | New cases commonly require another filter, route, mapping, or workflow. | +| Predictability | Model outputs are probabilistic and require constraints, testing, and fallback handling. | The same valid input normally follows the same predefined path. | +| Exception handling | An agent can interpret an unfamiliar case, ask for clarification, or escalate it. | Unfamiliar cases normally need a predefined error route or human intervention. | +| Best fit | Research, document processing, support triage, content transformation, and multi-step tool use. | Record synchronization, notifications, scheduled transfers, and stable application-to-application workflows. | +| Main operational risk | Incorrect interpretation, unsupported model output, excess tool access, or variable latency and cost. | Brittle mappings, unhandled branches, API changes, and large workflows that become difficult to maintain. | -This architectural difference affects how each category handles ambiguity. A rule can reliably evaluate whether `invoice_total` exceeds a threshold, but it cannot interpret a loosely written customer email unless another component first converts that message into structured data. An agent can interpret the message directly, although its output must be validated when the workflow has operational or financial consequences. The same boundary between judgment and fixed logic appears in comparisons of [AI agents and RPA](https://www.sim.ai/library/ai-agents-vs-rpa). +[Zapier](https://zapier.com/ai) and [Make](https://www.make.com/en/ai-agents) increasingly support AI-related steps, so “AI-native” and “traditional” describe architectural emphasis rather than permanent product categories. Adding an LLM action to a fixed automation does not automatically make the entire workflow agentic. -## How do Sim, Zapier, Make, and n8n compare at a glance? +## What is an AI-native workflow automation platform? -Sim is the AI-native option in this comparison, while [Zapier's workflow model uses triggers, actions, filters, and paths](https://help.zapier.com/hc/en-us/articles/8496180919949-Filter-and-path-rules-in-Zap-workflows), [Make provides visual scenarios with routers and filters](https://www.make.com/en/pricing), and [n8n combines low-code workflows with expressions, code nodes, and AI](https://docs.n8n.io/build/code-in-n8n). +An AI-native workflow automation platform such as Sim treats models, agents, prompts, tool calls, and context as first-class workflow components rather than optional actions attached to a rule-based pipeline. -| Evaluation area | Sim | Zapier | Make | n8n | -|---|---|---|---|---| -| Primary design center | Model- and agent-driven workflows | Trigger-action business automation | Visual, scenario-based automation | Technical workflow automation with AI capabilities | -| Typical decision model | Models, agents, tools, and explicit control flow | Predetermined steps, filters, and paths | Predetermined modules, routers, and filters | Nodes, expressions, code, branches, and agent nodes | -| Unstructured input | Designed for model interpretation and extraction | Usually handled through an AI action added to a workflow | Usually handled through an AI module added to a scenario | Can be handled with model, agent, or code nodes | -| Runtime adaptability | Can select tools or paths from context within configured boundaries | Usually follows paths created in advance | Usually follows routes created in advance | Supports both predetermined logic and agent-driven behavior | -| Deterministic automation | Supported, but not its only design goal | Core strength | Core strength | Core strength | -| Technical extensibility | Open-source codebase and self-hosting | Managed-platform extensibility | Managed-platform extensibility | Source-available code, self-hosting, and code nodes | -| Best fit | AI agents and workflows whose inputs or paths vary | Straightforward SaaS-to-SaaS automations | Visually complex but mostly predetermined data flows | Technical teams that want granular workflow control and AI nodes | +In an AI-native workflow, a model can perform tasks such as: -The table describes architectural emphasis rather than a hard product boundary. [Zapier can combine if-then automation with AI steps](https://zapier.com/blog/ai-by-zapier-guide/), [Make offers AI agents in its visual platform](https://www.make.com/en/ai-agents), and [n8n's AI Agent node can choose tools](https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.agent). Sim can also execute deterministic steps. Buyers should evaluate whether model-driven reasoning is the organizing principle of the workflow or merely one operation within it. +- Interpret a request that does not follow a fixed schema. +- Classify intent from the meaning of a message. +- Extract data from documents with inconsistent layouts. +- Choose which approved tool to call. +- Decide whether enough information is available to continue. +- Produce a structured result for a deterministic downstream system. +- Escalate uncertain or sensitive cases to a person. -## What are the key facts about Sim, Zapier, Make, and n8n? +AI-native does not mean every step should be probabilistic. A reliable Sim workflow can use AI for interpretation and decision-making while retaining deterministic branches, validation, approvals, and fixed application actions around it. + +## What is a traditional rule-based automation platform? + +A traditional automation platform such as [Zapier](https://help.zapier.com/hc/en-us/articles/22234847450893-Zap-workflows-quick-start-guide) or [Make](https://help.make.com/whats-a-scenario-and-which-type-should-you-use) executes predefined triggers, actions, filters, mappings, schedules, and branches against expected inputs. + +A typical rule-based workflow might state: + +1. When a form submission arrives, create a CRM record. +2. If the country field equals a specified value, assign the record to a regional team. +3. Send a predefined message. +4. Add a row to a reporting system. + +This model is highly effective when the input schema and required outcome are known. Its limitation appears when the workflow must infer what a person meant, interpret a novel document, or choose among actions that cannot be fully represented as fixed rules. + +## How is AI-native workflow architecture different from trigger-action automation? -Sim, Zapier, Make, and n8n differ in licensing, self-hosting, and cloud usage accounting as well as in workflow architecture. +Sim places model-driven interpretation and tool selection inside the workflow’s control loop, while [Zapier](https://help.zapier.com/hc/en-us/articles/22234847450893-Zap-workflows-quick-start-guide) and [Make](https://help.make.com/router) generally place predefined triggers, branches, and actions at the center of execution. -- Sim is distributed under the [Apache License 2.0](https://github.com/simstudioai/sim/blob/main/LICENSE), supports self-hosting without a platform license fee, and offers a hosted service with usage-based costs; as of September 2026, current hosted terms should be confirmed on the [Sim pricing page](https://www.sim.ai/pricing). -- Zapier is a proprietary managed platform, and its cloud plans primarily account for [successful automated actions as tasks](https://help.zapier.com/hc/en-us/articles/8496196837261-How-is-task-usage-measured-in-Zapier); as of September 2026, confirm definitions and allowances on [Zapier's pricing page](https://zapier.com/pricing). -- Make is a proprietary SaaS platform, and its cloud plans use credits to account for automation activity; as of September 2026, confirm credit consumption and allowances on [Make's pricing page](https://www.make.com/en/pricing). -- n8n is [self-hostable](https://docs.n8n.io/deploy/host-n8n) under its source-available [Sustainable Use License](https://docs.n8n.io/privacy-and-security/sustainable-use-license), and its hosted plans primarily use workflow executions as the usage unit; as of September 2026, confirm terms on [n8n's pricing page](https://n8n.io/pricing/). +A simplified traditional workflow looks like this: -Pricing structures, included usage, and plan limits can change. Teams should use the linked vendor pages for a current total-cost calculation rather than relying on historical per-unit prices. For a deeper treatment of the licensing distinction, see [Apache 2.0 vs. fair-code licensing](https://www.sim.ai/library/apache-2-0-vs-fair-code). +`trigger → filter → mapped action → mapped action` -## How do AI-native platforms handle unstructured input differently from Zapier and Make? +A simplified AI-native workflow looks like this: -Sim can make unstructured text, documents, and model outputs part of the workflow's decision process, while Zapier and Make generally need those inputs converted into fields before conventional routing can use them reliably. +`request → context and constraints → model or agent decision → approved tool → validation → next decision or result` -Consider an inbound support request. Customers may describe the same billing problem in hundreds of ways, omit account details, combine multiple issues, or include irrelevant context. +The most dependable production architecture is often hybrid: -A traditional workflow can send the message to a model for classification and then route the returned label through a set of paths. That design is effective when the category list is stable and the required action for each category is known. +`deterministic trigger → AI interpretation → schema validation → deterministic policy check → approved action → logging or human review` -An AI-native workflow can go further by allowing an agent to: +This hybrid structure lets Sim handle ambiguity without giving a model unrestricted control over the entire process. -- determine the customer's likely intent; -- identify missing information; -- select an approved account or billing tool; -- retrieve relevant context; -- decide whether it can proceed safely; -- request clarification when confidence is low; and -- escalate cases that fall outside policy. +## How do AI-native platforms handle unstructured input better than traditional automation tools? -The AI-native design requires fewer branches for linguistic variation, but it does not eliminate engineering. Production workflows still need structured tool inputs, output validation, permission boundaries, retries, observability, and human approval for consequential actions. +Sim can interpret the meaning of unstructured text and documents before converting the result into structured data, while traditional automation tools work best after the relevant fields and rules are already known. -## Can AI-native workflows adapt without being rebuilt? +Consider an inbound customer email. A fixed workflow can reliably route the email if it contains a known label or comes through a structured form. An AI-native workflow can also determine whether the writer is reporting a billing problem, asking a technical question, expressing cancellation intent, or combining several requests in one message. -Sim workflows can absorb some variation through model instructions, schemas, context, and tool selection, while conventional [Zapier paths follow user-defined conditional rules](https://help.zapier.com/hc/en-us/articles/8496288555917-Add-branching-logic-to-Zap-workflows-with-Paths) and Make scenarios usually require explicit branch changes when a new path is introduced. +Useful AI-native input types include: -For example, a lead-qualification agent can interpret a new way of describing company size without adding another string-matching rule. A document-processing agent can often extract the same fields from several layouts without a separate parser for every template. An operations agent can choose among approved tools according to the request rather than requiring one path per request type. +- Free-form email and chat messages. +- PDFs and documents with variable layouts. +- Call transcripts and meeting notes. +- Natural-language internal requests. +- Research material gathered from multiple sources. +- Records with missing, inconsistent, or ambiguous fields. -That adaptability has limits. Teams should rebuild or revise a workflow when business policy changes, a new system needs authorization, the output schema changes, or a new action creates additional risk. AI-native does not mean self-governing, and a prompt should not be used to conceal a material policy change from review and testing. +The model’s output should still be constrained to an expected schema before another system acts on it. Interpretation can be probabilistic even when the resulting system action must be deterministic. -The useful distinction is between input variation and policy variation. Models can often handle input variation within an established policy, but humans should explicitly define and approve changes to the policy itself. +## Can AI-native workflows adapt without rebuilding every workflow branch? -## Are AI-native workflows less predictable than rule-based workflows? +Sim can adapt a workflow’s behavior through revised instructions, examples, tools, and evaluation criteria, while Zapier and Make commonly require builders to add or modify explicit routes for newly recognized cases. -AI-native workflows are less deterministic when models choose outputs or actions, while rule-based workflows are more predictable because the same structured input normally follows the same configured path. +For example, a support-triage workflow may initially recognize account access, billing, and product questions. With a rule-based design, adding several nuanced intents can require more filters and branches. With an AI-native design, the classification criteria can be updated while the validated output schema and downstream routing remain stable. -Model variability matters most when a workflow can spend money, modify records, communicate externally, or access sensitive data. An AI-native platform should therefore be evaluated on whether it lets teams constrain the system rather than merely connect a model. +AI-native adaptation is not automatic correctness. Teams must retest prompts and model behavior because a broad instruction change can affect cases that previously worked. -Useful controls include: +## Are traditional automation tools more reliable than AI-native workflow platforms? -- typed inputs and structured outputs; -- restricted tool access; -- allowlists for permitted operations; -- confidence or validation checks; -- maximum step and retry limits; -- approval gates before consequential actions; -- test datasets for common and adversarial inputs; -- execution logs and model traces; and -- deterministic fallbacks when an agent cannot proceed safely. +Zapier and Make are generally more predictable for fully specified tasks, while Sim can be more resilient when the task itself contains ambiguity or variation. -Rule-based automation also fails, but its failures are commonly caused by changed schemas, revoked credentials, API limits, missing fields, or an unmodeled branch. AI-native automation adds model-related failure modes, including unsupported conclusions, inconsistent classifications, prompt injection, and inappropriate tool selection. +Reliability depends on the failure being measured: -## When is Zapier better than an AI-native workflow automation platform? +- A deterministic workflow reduces variation when valid inputs and rules are known. +- An AI-native workflow reduces brittleness when valid inputs cannot all be enumerated. +- A deterministic workflow can fail when an unexpected format bypasses its rules. +- An AI-native workflow can fail when a model misinterprets context or produces an unsupported result. -Zapier is often better when a team needs a simple, managed, deterministic automation between common business applications. +Technical teams should test AI workflows with representative examples, adversarial inputs, malformed data, tool failures, and low-confidence cases. High-impact actions should require schema validation, policy checks, limited permissions, or human approval. -A conventional Zapier workflow is a sensible choice when: +## When should I use Sim instead of Zapier or Make? -- the trigger and desired action are unambiguous; -- source data is already structured; -- the path changes rarely; -- the workflow must be easy for a nontechnical operations team to own; -- a managed connector is more valuable than infrastructure control; or -- the cost and risk of model reasoning would add no value. +Sim is the stronger fit when the workflow’s main difficulty is understanding context or deciding what to do, rather than simply moving known fields between applications. -Examples include copying a form submission into a CRM, notifying a channel after a known event, adding a labeled contact to a list, or synchronizing fields between systems. Adding an agent to these tasks would usually increase latency, cost, and testing requirements without improving the result. +Use Sim when the workflow needs to: -[Zapier can add AI steps and agents to its automations](https://zapier.com/ai). The relevant buyer question is therefore not whether Zapier supports AI, but whether a fixed sequence with selected AI actions is sufficient for the use case. +- Interpret natural-language requests. +- Extract or transform information from inconsistent documents. +- Select from multiple approved tools at runtime. +- Combine model reasoning with API calls and deterministic controls. +- Run a multi-step agent until a defined completion condition is met. +- Produce structured output from unstructured evidence. +- Support self-hosting under an [OSI-approved Apache License 2.0](https://opensource.org/license/apache-2-0). -## When is Make better than an AI-native workflow automation platform? +Use Zapier or Make when the workflow needs to: -Make is often better when a team wants a visual representation of a complex but mostly predetermined data flow. +- Copy structured data between common SaaS applications. +- Send a predictable notification after a known event. +- Run a scheduled synchronization. +- Apply stable filters and field mappings. +- Process high volumes of simple, deterministic events. +- Remain understandable to operators who do not need to manage prompts or model behavior. -[Make scenarios support modules, routers, and filters](https://www.make.com/en/pricing). If every branch can be described in advance and the main challenge is moving or transforming structured data, a scenario-based automation can be easier to inspect than an agent choosing its route dynamically. +The decision should be based on the workflow’s uncertain steps, not on whether the team wants to “add AI.” The [best Zapier alternatives](https://www.sim.ai/library/best-zapier-alternatives) guide provides a broader vendor comparison. -[Make can incorporate AI agents](https://www.make.com/en/ai-agents), but a model call does not automatically turn the surrounding scenario into an agentic system. A workflow that classifies text with a model and then follows a fixed router remains mostly rule-based. +## When do traditional rule-based automation tools still win? -## When is n8n better than Sim? +Zapier and Make still win when every valid condition can be specified in advance and the workflow benefits more from predictability than contextual reasoning. -n8n can be better than Sim when a technical team primarily needs granular node-based integration automation and wants to add code or AI capabilities to that foundation. +Common examples include: -n8n is an important incumbent because it combines conventional workflow constructs with [JavaScript or Python code nodes](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.code), [self-hosting](https://docs.n8n.io/deploy/host-n8n), and [AI agent nodes](https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.agent). It can support both deterministic integrations and agent-like workflows, making it a stronger architectural comparison for Sim than products limited to simple trigger-action recipes. +- Copying new form submissions into a CRM. +- Sending a notification when a database field changes. +- Moving files on a schedule. +- Updating a spreadsheet from a structured webhook. +- Creating a standard task after a fixed lifecycle event. +- Running a deterministic approval after all decision inputs are already structured. -Sim is the stronger fit when AI agents, model interactions, and dynamic tool use are the primary design problem. n8n may be the stronger fit when the workload is predominantly integration orchestration and AI is one component among many. Teams comparing the products can also review these [n8n alternatives](https://www.sim.ai/library/n8n-alternatives). +Using an LLM for these tasks can add latency, variable output, testing overhead, and model cost without improving the result. AI-native workflows should reserve model calls for steps that actually require interpretation, generation, or runtime decisions. -Licensing is also materially different. Sim uses the Apache License 2.0. n8n uses the [Sustainable Use License](https://docs.n8n.io/privacy-and-security/sustainable-use-license), which makes its source available under fair-code terms and restricts some commercial uses. +## Can Sim, Zapier, and Make be used together? -## When should a technical team choose Sim? +Sim can handle an unstructured or reasoning-heavy stage while Zapier or Make handles deterministic application updates before or after it. -Sim is a strong choice when a technical team needs an open-source, self-hostable platform for workflows in which models or agents interpret context, select tools, and determine the next step. +A hybrid customer-support workflow could work as follows: + +1. Zapier receives a structured event from a support application. +2. Sim interprets the conversation, classifies the issue, and drafts a proposed response. +3. A deterministic check validates the category and confidence threshold. +4. A person approves sensitive responses. +5. Zapier or Make updates the ticket and sends the approved result. + +This approach avoids replacing stable integrations merely to introduce AI into one decision-heavy stage. + +## How does n8n compare with Sim, Zapier, and Make? + +n8n is a visual workflow platform with [self-hosting](https://docs.n8n.io/deploy/host-n8n), [code extensibility](https://docs.n8n.io/build/code-in-n8n/using-the-code-node), and [AI-related nodes](https://docs.n8n.io/build/integrate-ai/langchain-in-n8n), while Sim is designed around AI-native workflows and agent behavior. + +n8n often fits technical teams that want granular workflow control and self-hosted automation across conventional integrations. Sim fits teams whose central requirement is composing and operating model-driven workflows with tools, reasoning, and deterministic safeguards. + +The licensing distinction matters. Sim is licensed under [Apache License 2.0](https://github.com/simstudioai/sim/blob/main/LICENSE), an [OSI-approved open-source license](https://opensource.org/license/apache-2-0). n8n uses the [Sustainable Use License](https://docs.n8n.io/privacy-and-security/sustainable-use-license), which is source-available rather than OSI-approved and includes restrictions on some commercial uses. Teams should read the current licenses before making a licensing or hosting decision. + +## What are the key facts about Sim, Zapier, Make, and n8n? -Sim is most relevant when several of these conditions apply: +Sim, Zapier, Make, and n8n differ materially in license, deployment model, architectural emphasis, and hosted billing unit. -- inputs arrive as natural language or documents rather than clean fields; -- the correct path depends on context that is difficult to encode as rules; -- the workflow must choose among multiple approved tools; -- the team wants deterministic steps and agentic decisions in one system; -- self-hosting or source-code access is an architectural requirement; -- the team wants an OSI-approved open-source license; or -- engineers need to inspect and control how model-driven execution works. +- **Sim:** As of September 2026, Sim uses the OSI-approved Apache License 2.0 and supports free self-hosting; buyers should confirm Sim Cloud’s current billing details on the official [Sim pricing page](https://www.sim.ai/pricing). +- **Zapier:** As of September 2026, Zapier’s official plans [meter Zap workflow usage in tasks](https://help.zapier.com/hc/en-us/articles/16051471305357-How-to-select-your-Zapier-plan). +- **Make:** As of September 2026, Make’s official plans [meter usage in credits](https://www.make.com/en/pricing). +- **n8n:** As of September 2026, n8n uses the source-available Sustainable Use License rather than an OSI-approved open-source license, [supports self-hosting](https://docs.n8n.io/deploy/host-n8n), and [meters hosted plans primarily by workflow executions](https://n8n.io/pricing/). -Sim is not automatically the best choice for every automation. A fixed trigger-action tool may be simpler for routine record synchronization, and a purely coded service may be more appropriate for extremely high-volume or latency-sensitive processing. +Pricing, plan limits, and usage definitions can change. Buyers should compare the vendors’ official pages using their own expected execution volume, model consumption, concurrency, and support requirements. -## How should teams compare AI-native and traditional automation platforms? +## How do I decide whether to move off Zapier or Make? -Technical teams should compare Sim, Zapier, Make, and n8n against the same representative workflows rather than choosing from feature lists alone. +A technical team should move a workflow from Zapier or Make to Sim when maintaining explicit branches has become harder than governing a bounded AI decision. -A useful evaluation should include at least three workload types: +Strong migration signals include: -1. A deterministic task with structured input and a fixed output. -2. A variable task involving natural language or documents. -3. A consequential task requiring validation, approval, and recovery from failure. +- The workflow has accumulated many filters for slight variations in human language. +- Operators repeatedly fix cases that do not match an expected schema. +- The required action depends on evidence spread across messages or documents. +- New categories force frequent workflow redesigns. +- A human is already performing the interpretation between automated steps. +- The workflow needs to select tools based on runtime context. -Teams should measure: +Weak migration signals include: -| Criterion | What to test | -|---|---| -| Correctness | Does the workflow produce the expected result across normal and edge-case inputs? | -| Build effort | How much configuration, prompting, code, and connector work is required? | -| Change effort | What must be modified when an input format or business rule changes? | -| Observability | Can operators understand the chosen path, tool calls, errors, and model outputs? | -| Governance | Can the team restrict data, tools, credentials, actions, and model access? | -| Reliability | What happens after timeouts, malformed outputs, API errors, and partial completion? | -| Latency | Is model reasoning acceptable for the user or system waiting for a result? | -| Cost | What do platform usage, model calls, infrastructure, and operational support cost together? | -| Portability | Can the workflow be self-hosted, exported, versioned, or moved if requirements change? | +- The existing workflow is simple and reliable. +- Every input is structured. +- The workflow only transfers or reformats fields. +- The team cannot yet evaluate model outputs or monitor tool calls. +- The action is high-risk and no deterministic validation or approval can be added. -The winning platform may differ by workload. Many organizations will use deterministic automation for predictable system events and AI-native workflows for cases that require interpretation or judgment. +Migration should begin with one interpretation-heavy stage rather than a full replacement of every deterministic automation. -## How do you migrate from Zapier or Make to an AI-native platform? +## How do I migrate a rule-based workflow to an AI-native workflow safely? -Teams should migrate from Zapier or Make to Sim selectively, beginning with workflows whose rule complexity is caused by unstructured input or contextual decisions. +Sim should first replace the narrow step that requires human interpretation, while the workflow’s triggers, validation, and final actions remain deterministic. -A practical migration sequence is: +A practical migration process is: -1. Inventory existing workflows, owners, triggers, applications, credentials, volumes, and failure rates. -2. Separate deterministic data movement from reasoning-heavy decisions. -3. Keep stable trigger-action workflows in place unless migration has a clear operational benefit. -4. Select one workflow with measurable outcomes and meaningful input variation. -5. Define the agent's permitted tools, output schema, stopping conditions, and escalation path. -6. Build a regression dataset from historical examples, including failures and unusual inputs. -7. Run the old and new workflows in parallel without allowing the new workflow to take irreversible actions. -8. Compare correctness, latency, cost, and manual intervention. -9. Add approval gates and deterministic fallbacks before production rollout. -10. Expand only after the workflow meets an explicit acceptance threshold. +1. **Map the current workflow.** Identify triggers, mappings, branches, external actions, failure paths, and manual interventions. +2. **Locate the ambiguous step.** Find the point where a person interprets language, documents, or incomplete context. +3. **Define the required output.** Specify a strict schema, allowed categories, confidence behavior, and invalid-result handling. +4. **Create representative tests.** Include normal cases, edge cases, malformed input, prompt injection attempts, and previously failed examples. +5. **Limit available tools.** Give the model access only to actions required for the workflow. +6. **Add deterministic controls.** Validate output and enforce permissions, thresholds, and business rules outside the model. +7. **Run in shadow mode.** Compare Sim’s proposed result with the existing process before permitting production actions. +8. **Add review gates.** Require approval for sensitive, irreversible, financial, or customer-facing actions. +9. **Monitor production behavior.** Track failures, escalations, latency, model usage, and changes in input distribution. -The goal should not be to replace every rule with an agent. The goal should be to use model-driven decisions where they reduce brittle branching or enable work that fixed rules cannot perform reliably. +Sim’s workflow execution model is documented in [How workflows run](https://docs.sim.ai/workflows/how-it-runs). -## Can AI-native and traditional automation platforms be used together? +## What security controls do AI-native workflows need? -Sim can work alongside Zapier, Make, or n8n when a team separates model-driven decisions from stable integration tasks. +Sim workflows that can call tools should use least-privilege credentials, validated outputs, explicit tool boundaries, audit logs, and human approval for consequential actions. -A traditional automation can receive an event, perform predictable data preparation, and call an AI-native workflow only when interpretation is required. Sim can then return a structured decision for the traditional workflow to validate and execute. Alternatively, Sim can own the agent loop while deterministic services handle record synchronization and downstream processing. +Technical teams should account for risks beyond those found in conventional automation: -This hybrid architecture is often safer and easier to operate than turning an entire business process into one autonomous agent. It also lets teams preserve mature automations while introducing AI-native behavior at the points where it creates measurable value. +- Prompt injection in messages, documents, and retrieved content. +- Sensitive data being included in model context. +- A model selecting the wrong permitted tool. +- Excessively broad application credentials. +- Unsupported or malformed structured output. +- Repeated tool calls that increase cost or cause duplicate actions. +- Model or prompt changes altering previously tested behavior. -## What is the bottom line on AI-native workflow automation vs. Zapier and Make? +Traditional automation also requires credential management, auditability, retry handling, and protection against duplicate actions. AI-native architecture adds the need to treat external content as untrusted instructions and to evaluate behavior across a range of inputs. -Sim is better suited to contextual, unstructured, and agent-driven work, while Zapier and Make remain better suited to many predictable trigger-action automations. +## Will AI-native workflow automation replace Zapier and Make? -Choose an AI-native platform when the workflow must understand meaning, work with variable inputs, select among tools, or determine its route at runtime. Choose a traditional platform when inputs are structured, branches are known, and deterministic execution is the priority. Consider n8n when a technical team wants a node-based integration platform that spans both conventional automation and AI nodes. +AI-native workflow automation will not replace every Zapier or Make workflow because deterministic trigger-action automation remains the simplest design for predictable tasks. -The categories will continue to overlap as traditional platforms add agents and AI-native platforms add connectors and deterministic controls. The durable evaluation criterion is not the product label: it is whether the workflow's core challenge is executing known rules or making bounded decisions from context. +The likely outcome is a blended automation stack. Rule-based systems will continue to move structured data and enforce known procedures, while AI-native systems will interpret ambiguous inputs and make bounded decisions. Some platforms will support both patterns, making workflow architecture more important than product labels. -## Where can buyers compare the best AI agent builders? +## What other AI automation comparisons should I read? -Sim's canonical guide to the broader AI agent builder category is [Best AI Agent Builder Platforms in 2026](https://www.sim.ai/library/best-ai-agent-builder-2026). +Sim’s canonical guide for the broad “best AI agent builder” question is [Best AI Agent Builder 2026](https://www.sim.ai/library/best-ai-agent-builder-2026), while this article is specifically about AI-native versus traditional workflow architecture. -This article owns the narrower comparison between AI-native and traditional workflow architecture. Buyers seeking a ranked evaluation of AI agent builders should use the canonical guide rather than treating this architectural comparison as a general ranking page. +Use the canonical guide when comparing the broader AI agent builder market. Use this comparison when deciding whether a particular workflow belongs in an agent-driven system or a deterministic trigger-action system. diff --git a/apps/sim/public/library/ai-native-vs-traditional-workflow-automation/cover.jpg b/apps/sim/public/library/ai-native-vs-traditional-workflow-automation/cover.jpg index 8d17c29309380a98a915b93d6330a21fd8ed0547..02ead8488652ce38c2e7510e51e0c6b78baf6e6f 100644 GIT binary patch literal 34893 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 literal 36709 zcmeFYbzEG_(kkng z2_DYtFEa%xDFrhZBRwOR2tU7wih_!rUD8`8VE@;h^Z`&{!1LeLK!A|~z)`>;P{3XW z0Qdkf05}8~7#QGR7Zmh6NEit4w_5anTmDyrmo)$a#OpiA5Xi3ujXnQ*?SFg&DQJk7 z^FMw|R8MrO2y&{TVg~SzY0ILVT0!6Ba&z~SDLZ1Gt-#&7n~ zWu>|ClHYhGUF{@4{r_|j6#ua-EmF0&iHTibeyUgv5ED)#zO>DmJ%fsNgW>!3KbkT- zuReDCm;cc#|F7}?n0)R501&C$96KKCK8n&@ zJp8GE>#1LR`l66T!uY@Xg9LL(&w=TsxZ5|MHK)cyNc{4@>i)jd+thP&1a;M8g#!#Ph@ zfQcIQa4O$vYd63c~C4#Vn`72BJlzmDVcjBfP2{T0;d0(~22?{X4GoPW5tcQ{v@} z;mrD{n+oU@*^flTb(f< zCA{oOOhS;-tfsOF0aZn@fFYN2dO+k_ObVbhwcNGmhHT1MEes0@fq>i;>mR=l(V@Cx zFoazU`^SVwrMJLDG^O5iro8~(3!Ur>MC`Nt+A_Nhgg0JFQZJZSzap8{I*JO8Yxf zLnPtf@rKEq#VZQ^scT z#|?mXrJ|2TOWu8e>Fejbn_RgqHVPEZIU_TqfPLPr7K%Dqr% zv1SveJD~~K9eLiXsm0UGedF)V&GV2A?W0~5-osp!&p#}S1?CY(LzZYWwlRI3QmC zzagKvzJx?)t!a7nx~JnhRt8sMPPTGvnPo;Bm1sq{9j6V^)}(*-P>d4F}Zsj`F4 z&|ON~DR(wDmCdN6?R_>6LfE29p^9e0;G}dCh>(uyuB&zJmYP?weuAM)V5&kg!$G`- zw8@u8ZnP7Ocq7S|=PH_$0J?t3YRaa|%<}sNcznE=6SU}oeyy;=%(ZiYW44H6w+K37 zqK2GgBQ$GJCYHt+S@C4WdLY!m?b!sZY!dj4wss+&a5zauEFeZte z(nZmztgqUNG&;4P!f07t%{$Ixt-l)HK{I#zzZCxa=~=kmnxQ0KK0=<%Zy*&$<8zV|rW}ry4;)5n)MV zn2i|r+0h(u+VoDCj2d%JeU!CRF+LU-TJz6e{>$0^SB2ODt;BET(6L8%j{@67dJrSm zK0aUGiU1eTuu_@+fnH@3gbg7DsHI#^++fh+ec!tIA zF1tk>TOCIX=ic1e|8%J~VFpw(WI43KGJaR7BiC4M6mk-lETJh6N;wb;t3QV4OTOIy zbh%?Qn$KdGdFo-XPpnDc641?e%}iR;XqFp!#!GemvP?D4lf!aT)&!pfyxL#~Zo z5g87ckIJ1KPfz!;alS2|KoYkP|FAcLgJ8KtxdVXd^j6ot4_^l-VnSq>b`K+zGYjK6 zhnqv2Ax$4_4+`Schr8*yN54sWz+H2=#wOxLkv{Z*QEn=(iTr58G;@;osGW0)3&4u*yj8T5;U?8j5eFP|b7I zt4V(x?COF>#0VJx0PemwS2`G?$8cO9br;giQSJ>FK;+o}ECe8VcQKyFX!rUI11i=p zq+z!$@{+i}>MgX*J-+q$*R>F$%_=MlG%Bbzih)=Ps2p4dGmNT%QT zA?}f9&ytx*0o1B}a53};spCTv6OW%4KVz(}o?fvBp{3w9Rw41|VGrb|e8Z~4OH{lgML zip0Py$Nkr3cwi`$&-tk->?gI7?U})$B*W2{?jER^IUo@ySnE1_fC}238OD!db&sS< zc6-NEgQ4$rb+jfkm?$+fKL(}i=$msR&wd6+SQ%*@&lGZ#RJsRm!!AyGp_zEh*h*uk%#v2svrnYfC(?DV_rO#?W ztE4UuzFcrRCVwz8k;bCT4?CR+B}y}{Sk8FB(U;HZfmbiDinR#ujgD9fTni|R?x4Cb z#Yi+mY>Wi11!3{(I!Vs(UCw5JoCv4Y>$#imbF1U+;DSvnRxynseP#oLnZ8ViEOJ_^ z7#(rb`(f3B$5l z!irCCXYs~}a_q)z`v$iYpvA<$|IkG3CIunM?zuR2)b-CKTGzL1pd2aXG{ z@sE_0s8n?~pY{;UcPfBlH6E1$zvv(#b zB7^mfkH9deF3%y#DO?|xujr)Buwf_nlD5I{SljHD&4dT(3*Z;^h9>kv_33nWU0`jK zCQCto$KD44BJ)u>f)*;xBOZMVN&!ei(>?1b+e5R8k^mI~L@bVm$ejeGmdh7_5SWYC zQ%FKHU_WxOgjO_(HXMFU(XnC3wwk8#GLIwUY3A{lO*5uILg^WPW~t7gbq1@en9j2( z{Vgv>=keu{qw{ef2@Bx$OpJ(qz*BAb7q9*gZlgwGQCxXhrkpxk{$v9dC-gJ_rS*}1=U=AOBE%O07T`;!V`dY}6!v2{Xf z=*XYtV&wBDeS`^FrkSdm)`<^Chq91D!0^&3`v@t807vT4do}6#G~1jIgE1~?(cbYn ziJyy!bVAV&#{~_%d1Z{xL=~WcR2H?}wAKRGqZdH7y}LuL1-{Wpl(%!&Viw_M*kxM% zy&nb35IVCzupfsYAg|PA3B#gwN}m23rck_Joi%R5YGk6ac4bFi$EpEHjE0mm?A9T% zCtPFa`Zid2yQBwE*Myc)ewA_Zx`Olhq3)!SvYbuf1rX=X=U6pJqfg&1an|^{RYKV1 zpuXC&nd~5b0_YB{)$NmuL< zwtI~W4H`0-@1254!}DJNUaKw|@r!9xFMv_LEK7|=$)myBv!@ycbAp@heX9~xa0!F= z^Z3EkCS?;&d1?VFXI9i125x~fcGsu^<3D1+-d?GJ^;Qe6VOQPGSWaCnP z^}OiE_)$<}rE*qdBRQ5L}qV8VC1Njs_F+9XWyLXI+RsZr6R*82r8 zw}q;0l{4d{$!$)?V=stYe&u_Y<$e}D7{9degJVX=v8pHQ{mK=MfMZvYf!waCqrX=DJG%7A4iDSyGlWEXlddTIHQgLy{u=s7E zf#+TE$-Bo0dzc}vy#%n1d)v~_U)X$=t~LdF{(LMjFUg(sP0bSWjpD#yc)6t$nhfw34VGsll4#xcyb$l_ z(Nn|(m5e8GI+r!nM6Ncf+5+M;Y1Nt2k;xd3x4V*MZh?Vfl72hz*@R32%?LY)G!G$i z)SFK`5h(+8wUHj9Yq3@*#|`{r@;fI9_QC4uaKD~WLrwR2;USJ(#xM?>wLK(c&o1a5 zERjMzd9vDH0B(3`&<>oHr^=D9r%q}WgHG(7Jy~Q3=spVWZWqI0HwbV3Y)<0)u6lkF z6L{$uCeWF)e_2jbyEsoh&>t5kWlC&AKx{Y-+LW?jA8s#o$~LY~ClyJpaCnLH;%tK7 zHLMG)yrU;28In@c5(R$17-xtjn$u^{6w{Ub1u$+;7X8mqs6Bv=8i~myEWVFEsZ-3+ zd@JYtEE6WI$OzLh8!f%iNhI#61L3q>%PC-rqBV8x7HueXdGi)7iQa*9IC?@R@Gk%* z%@J7+G1g#)5BjzE+X}t9d_q~MdS8c8Dc;AeGJZCslg3W)(6m z_iE$gY^cog*U{tep;E`%%}M>pQWZvPz+1=ABJ#P0>=N=}#uT4qY=4`0HwKF`%rBH0 zyZCg)XIB@1j`WHe345K8zS~lAEyK!sA}uKK(&fm6q)uvt_{{+0GBH9lbXik zZr>H#R*h(?Tx4S7_1ul8RaL=amVpxxX_sNB9-i;3WImHmZr7?0W{&z^{PzEC`Mcv` zDfXIdHwb~!`1cg0)@8(DbQKRK0NAuFs-kK66w{0)i;P2SNHdtfF8N9OpjpP(ljanm zbJP7tBcY)bca$TJa%th>&{(5}4FL>>Zr$yY*Bmbb*U9^NjT8p9_~%Lis$|amuG|W! zs4v~q6iiu*bw57a5yw>A6pYV_wVi0xTbFV`pl;*$7??A8NEAm3_d^baXP7uoSGOs+7v5qE!kV&vKUve>46(Dl076R1S~J+-AfM@l;iqg%oW zctubHP1n_&LITIJ6MRCZyiqdh$}OhIOulYJf=;ah<$iu5ePlDkjw>rWC~GU@B3XgA zD?sNP`0y1J5W)G8!bjjO?cenRGl&hayy&DjB-|tlKLjE#a`D9gAR#Jrl?EWfCjr{v?F2RME*}tza zBpKF)4LJwMF%T5iJ6!ZM*ca4@hnukD@}z{eDEao)r9@9x@G zR7L9*bm53pekKW2rSkM>eC_Yq;wqM9mlNoVy1d82(+RPj%e|HlM8_5bA{o+|bcIfi z&ht~RVLLNUp}aNcYQt$eSoqQW;l5YVIcBT@@226BP%Lje?ITX>nEMJBXrR8DCm3># zv8-bDDj86f3vsA2evowYzYD*sE*(z-M#f@+BidJ3iK7fByeS)-$-uOWnE~adTj(Wv zOO>z^H3Ux1=U<$Byf1*1e-W`2nfP;;lT8$;YmUGGI;zS`l2|F=kY4i>asAj;N#10r z#dwg(*vxC6EfLa9rC^*8 zZZJ*LWbK0&4f#3fhU9Qfcm&7;6fQUW?oC$)6%ubtnw?JpTWC(xkvfIB7kw?;wHU8`2 z6KMPguwiy@v+sei?gGTmxX-oC67?M`_~p z5C?&nmiHIuM`zy!=g94OX380X*pzH!*V%Ok6+x44eHI%C)4FKvAU|_ zYD33S5}0$Ke+KENo&yAFdpJ=u3PTJu+;qhv+)axn-lY^Vq#%g;#Y$pOyTE&p@TfO~ z$*?xWhu2)f(o90s$4}llotstYm$p@Ynvc9*)t-q=w2>Pw;l)CTby{OZw{kKt!tzsa z%s6RVoiaEGXJN;oJQ2xwK%pcm?+bDbCx8v^Kmo|Po~YQHI_N%j{b#YCLoDCh@hyPp zZG~g`w(!HW%mCIL3Q;{o<-vwrY!jxcT)e1Xwe*eXhRgmrt>-Ou?>+PEQPp(hbKv=5 z-W#DJCD}*pag94)stn{BmsuHN5`du@C(gsF8+vaYrR>tmbEVgO|J-Jk>+K+1K43~^ z${iIz)G^{$-wAzI@)i4fd^W}LBm4|=(dd_0KGea)MGf{PpaFD07it%_S&jnlnA4a{ zDzc7K;yj{?ZT$ezgzohFZsuSmU#2b#e+3~XW0jNWLy0G;NFYd=AVm9hE|)`Z(t0zu z3I18K=|^`<#?ZrZ?oxG?_$9Ke5vGW+I_Cv&J0(#(u zu%h3M-9pbI57&O}pA8XW??Vp$t)h^yOoaWMG9E@oMXd^ZwIxg!)ISx8&N6IZR1NAc zQm3_|*KSC{NR7;YXMf`%zSPJ#zYig|heB#c9!^(o8nKTfLNr4K@cNk}aaABzJI~T} zPyGYfmsVaz;36Y%sBKiO5h%hR3 zh&7t7R>G|~SyV&g0bGfCV?YZZ%wV5pb&pRB>+t9k9^b07TGNy?a^VElG#3R%g4Q|i zjJfN&5sZ0+0oM?7XNUsi*EF_IaQO9?zQUNbvBsvj*N&y6D{Ar!Dg6C9gAl}@JG?BQ z)6G)fZGLANvEnk7(`kQci_aSt zNv_cHyo2uV{E)i6t-1Hv0=tuOhN1l2DuTX}rvJsyJxn25ToH7gXRG}TXqDf)s6!^< z=7>w)8QsPfiOD!h#Q}Ux;fxZ$6C6mDsn#R)q3UXvMwG@%s;)m*Dq5Q;``fVB^9VOR z9O9NUv_?H)%gSp<{@h~SPt=?#m|`c2*i{>{drB-*(K42HHJs#QuCVN!;jb$|)HR_0 z5i}4PY{kw?4J-Au`w9u}Z+A6iT&4lf>Jw`#<11?*6$WUseuES*!!l1fHR06%Sc&NJ zI|};mh`KjZ$B2bJopIcM=v?6_1&SMTB{66Crn$Q3S5SPMskw+Q;8w_^3RH_+7wATJ zolWx+WU!~3xp)E8u^&x0)f`CWZj>S@asY*k3{k`v8^2|#ScU%O$FGm4PMy#c>n#6t z_j-ID5&e13U6a#@gB;DK$~ssFbXh#V(MCI1rLq_iG+O)~hk+u8VV2tArqyq>WE72| ztCC|i;6`T$Oj6{(QT9fpV{v3dM1WC1$ZW7!D9M9@ElOq1dar|j+{>4UN}kggFfePm zd`(`NIH!G-7?txGMquZHgKvh{yklcy(vAr?P#VU@T1Y`GDz2>-&qZ5_hPKzhBFdyT z(OLFvwY{jjbYr)=tg|T`%)jZEKKCM*Iso>*N0_qe@B+LgQO8lhml!X>p4bD%yDUsR=f!iuTnTO722BOQb|SeLaZa=u7oCw4iPi2+6nniT7C3>s%OWLc;`yQ{>EX?|Edo{MEvF zBb&497Rk-?fu4t_NwwM3sG&_?5v-WdWZI*QCVwhw8f{7r1F zJJ<3(RtyJ^ZgGj34iSa0VhR*KyAS&_jQwa4n+ysF5_KIP>!0j!G(G2Psor$r{+rkN zsy#1dv;HxBa$UwQLfbStQF7r9_R@c%q{W_s%B{+L+L<>61XMAiQC9(c=Wi|+cl+%+ z_s)0wpN+{f)7V2jrhNPXcC^%2>$M>#pY05?3eggu_Qd{VrV4f?dn+I~tTa?=j^26;L#oKN21~!T;L&z7 za{2LR(WNILu%+tM%}a~S5R81-xQCo_n@dP3K_6S`>YG^B8|IblbG`9t6~lQ`;G-&^ z01ZyzmsHS%7KB8fVo~X~HSlB0E+8J?A>B}Dttz%i$6Xb}!yBKmxabD^a|X(3rq+MW z_|^N4*j-BKk3G$CGj<%ExmNji4lP2ni~lRiOP>c z`Lo&9`AYa`q>>`r)N;MAxon2yTZ&B5Jv2MEz&88_GepD4*zj!`mTzy1{`?1&knVsd zm_)H}ebvcL2g5yiTW(E@Xv~;qN=mku)$d>;+el5!Zxu1JNkKj3>EKP((RJT+Qz&Kn z_afYqJE#xayu7N2&n#LS!7R}SR3$_=I%>y?2}u-w-u;-r)LAF8caPICD;$)@XV>Ab zT@#NvOEHji>6?$dYFQ>&`DMVE^^a;>L;dj@EvG5zXe{hoJ2;2us>bdFjTlb!q0J+& zD99UoZ`ZqubY{v)cYB-?pM|c*+=@9B8ZKs^VahdCa!Eg!$#M=A`VcO$t7?=_I&LP~ zw6PQME;&Rns5?z}1oE(*q|R|%A)b$!{Gu-OcOa4NQ-oU5LTj9CR+z<5s>quH3*lX> zYVY>0f3s6$(~C#o_Jg;B`iuS{YW-sSnp))QA@rCw5KDa*Lm=WoEJ<3}nqt%1XpuTE z+;`FxJBc+SJD-+jRE=MU2sYG^Q=z&9pH?U^4SZB?3Su7RATOkOdATi$Ma+vYxcmvr zq8v-Xi{w#IbF6%jT~+7379R5jx2u~MdSS*~v^oL7(xxu11zf7Es^M?5N@;%cO^2KI$@?UX(CYc{A~m1yERB*JJ&?3zk?6-YA;G2nC#xkGJ!O)gWv`}^0J z8M!N$F=zwMC3$emi>c>oy`_3{Xwvd^Kq$S6zkX|tKb4}+MR=N(W@W((S92_snn{W_ zC=9h7HN61NNPP%}S*CLkan$M~1BRFb3}IxsGK}$GQ~#EJecC0?M?{kQq{%I%4x{#t zc(=dkO~Dlb$jGfq7+#y0C0wR~&m_v((jqU>A-xEwU1${i(8 zq(eBH>f30Ok?S4PCc};)!x)PyS{)Vs?m_GwEf|Pf)eIo3K1tn35})k1dx(8Tlh?jz zK|;+@PjfIXt?2Z_TJ6_`fbUT`P`S*l3E!frs8@|3*PQsB+7ID;>(7)>676v$IQSBg z_?9IBE9(f2G|aP$-cI)amRflUe>~;V51p{qbrV6s!7&51$G}Qj1v+)Pr}lsLvG1W~ z56>dZBrWS=38 zJtcU8C(M#i5lWi3wq9d}-TVR&ZQA`dw*K>@X`w)En`Q^n0vb^hfqCAd~F7ePl!2*JVNRf z;x4ID*>w5lmukX?W-4ZMuC2|uii+smpe-$W8%jpPKdu|l$p@BqV>i*y^lN$XH2F5G z`{?4EX;5FclPvzB#=4=TUbEIX$2XRy2B9hWn06^L$`Ij#lR)`|lXlxrBK7#ujU*Iq z0e@)X=FdfROdwb2)1vzv#9^e^)<)K^4rt?w^>sgfu4VvVbfhySx%vYL1l(E59 zqkXjbMEv#UL!q`dy2Aw(ZmXtudQ`VoJt1^$p@3Q-zno30iDhq;ZAnrEW3j4N&b$67 z36rqC3>|7sa68)?2O%SP++wrjlM%5jePb7)qvN`|DFnEuzRT9*-^oc}ZiNY*q*otu z{mVJx|Io?>*0$s}JzIbJnY;MW-W3m}de&OEtdvZ*Yu#7M?fNsg1$yhPO83MVPLOf- z0-Fya4Fx|cQ82ekGgq(M#`WX=I7tm`0IOaG)2!-z`dGe^gv_FXG;~4BM2Yhje z16p8A#Lnm{TrTyh&xT*92{)0Y(C2fQy;ob6>O9*)|1GxVJF)5P1TF;>N~5sg=Vk=1R>IwXWsQDV z*8!46L;@C6y1vsmx`W{gU08guJTv-rG9{X0hp66?SP`nMZ4^a~k;MKE*nRu8RZ?VL z=1N&0?{`|Q&3iS6aeIX{mqBaJg_=?+rifztaQ)6vmI%NSer*xw+$bvsOAx{wfwsDv zy6Y=G_FI}%5}oOy*71Fnt}Tantpp(}f*xqd5tnyk`S|-Ww`7*mlJ;{0lBp^EkTEc} z_W-XVA1j5pIYU$!DO$}9PY==W`JKtG@O&1Ivh^NYg|ZYEqvC31*-=vZ zyu;_w${Yz&ss_!UdEXkB>BPU19`#^0euieWyTc>=6v|$mg0TR$+`lY|@GV&vJza_p zH29D#{Mnc1TtJ7LIF_brK}%!RDc>D5*TDkcTCTXBND2$B>eN@VDQS^G+EiZIw7PBG zH2t=x{y?oew;(3aU^Q1dRPb=N>4{eoD+(+A`Rm4-Y&J|A(IbIGUJ7_nmXq*=dvYtA z2PPi{wOt_fO{Q)>U}O+Jh<249Vh9IqwI47+V6BIR&t)?`}FP<{g{Ifq!=}{x0*!=0u>e)HL^c1Cr77 zMMU{__VWFKa7;RM0rUaKsmf2>BJ-1)+;NbtQ-9FCh_1X|Q9TQWB4ESsXSQpWtU=Sd8x`~tF*?-PSs*st><#tP8y%nlRC&tE@C`)WjD$`MN?OBQ>MEVJMI1k%>K1 zktJb#d{f>_Eud%Ras(9na)!$EuSd<;L(D(?1`B4Za4*bb26ROlUI0-Hf0@HXK88p< zCqLUZA2^*}5$y9?2kz8?aT#qj^P}#f=dO;AV#sshF=y1}>D>nP;ZUIwpKzUl+U$L0 zw_o)gBsUXMr|?UEG^%>50QEIAVDDDLZtgLH03?$ zD?@(f4AU%xsGlh{&3MN~FIzYfIYA91|~180}rtS4W?Lv5_D3g|5` z{RhS8%dqE|Ikb)~Dz{38Vrk?VG1i$J$KnL31{U%#FlRX83uhoxxYSu&`qb_Yr}{AxfOGP7k!Vr5n(Y?dZExVvNiZ?b~9dKX)! z#V#WwQ>S=*hp`<0g;f%`xNO=yrMHov9Jw2@YVE^X)<=dcg7~&6v5Shh7kAQ$Du&*o zzONR&Hs|{&=3Sj%#SyZFW+GUQoS(HeGqy034qAp>I2V|5wTEP0?d92j>B*Du4~vrM zD$H2mK+c7F3&p+^DI1r@@u7MMDu?evxqzldmpZv@srjF3c0%Ib(keXc|-9R)Tcffv4JpBRS2cx}&hePD5@x1ggsQ@T(LN>hU*bSy9;Dvp$OmGMUCkI67_-)xM`c9mr0^U6wtW&Cman=%%% zX>fo-s3g7FuoK$-->uwpT;@G&e`VnWn>d+}yi;gI^91^QT^6! zsf<0Mad9eE!Z`8JN5>eYmd_D=i|S@p5-hajtI{9~I@i@tm3k6}7_ z=omw3^W&?vZIxz1U=qmRe?1xh9L6(Z->F7qB8z=H`>IL($5#5^9os@?>65T02Tr3z zbyr12m6|5cE1<8-Og0peu5&{WxNrP8?CV4>x2LY9mG@wek8rjd1!LNaQ>nV|3(au; ztG~X$oY|lFL2cZ7vPuxt_)df=3rI&vhQy8HLF3rF1a8XCrt`~Y3!{9cS??N$Ym&$^ncd63P(YrvJ@{v) zeO$BHQ}F|u*zal(gr2CGl>Cwe@e!$eh}2?_f9i&RJV#W2a$OF!zlrBwX93Efl}Iy> zWirPm4T+(ZwuOE$i2REcr{=3HRMf#mltvBtCPw+8VhZur)G^De#D-P;3HA73q7B6` zo&t+2Tvg>%YHjK26(}`z*hw0OcExnF0XD{T;&5z^5*c&`Tvy?u$_kAEN|0zd%4_Sj zY1Lh)R(Z${!s0kO4HrR{RZaI6r0M3ZO$h^!(!37r-vd1|9ge zQO=5AKcg6Djh zwZ#)SG#I?m8EzWgsd<-1Pw;>~iaLib@^PaTli-6+s-|Q7nvgiIh#-`Jz2AYZS6hv7 z#up5N1GKG=Lhadq{Y$W2)^=w1RWkHsQ;xJ!w87*wly`WJ_a5~)oC>4cP9GOMK^p*P z=F=OT=(7}KM0@18-P(C2#klyU`qefw_a(y0|M@9iY%o=fki$1YpsJ4S+QQ3Pp+s@9cJXK0uXwsOg2xG}Q{@+791F zyQ;xj4Qlh(>t)n4K(@CM+TSj+W3Cz-=TM;zq2nlvS^eXG-ZbZxJ5qwN@hgCGMrt9>QF z@CzMR;_B3v)mi7*`uloC0_Jj4ca>W*)@jcgk9V5FzvZddQ~HF+wl?#r91(*Mp7n(Xa8qJ{c&)Q+YS z@txbIo!6$FWBA>|0~%l;71p0Nyhr*0sGPB<8C&B5bT{Hro0cy7!c$+!iF<7@O*O(gtP3_CqAV-ZoW-;8(#MRNF=c|hBx-WFHk7_y^*osF4=pR7HGy6;yp zWlt<6YIU0@%QeH?mn7Px)kB8C7Y7X59yYqLz*5J4)DVXR?6U$_6m04pei} zFVbIkB9#9`hAEA*IWJ5(ND(LLr9hm?`pL&7qt#5QxFlT0aKQkSrBDg~t5wfq0ds<| zF1Nr3n!7yL*7Idk>v;oTsZZT=ZBtIZ3G%rF#9JvRkt~J4+a5rOg)`{_Qc(hrA*@we z3OC$EkJy++YJ{v{>}8o^AZ?-dZL)W=cA4dGMa~73VHAPEL6xpb?TN@7%bflAVY1mjKLM9>wB|n!@t6YB+Kyb++*No#{X3Ocx%Go3?K)8sn zIJFf~9yvZ$<%R3esEsKdY9B$NG`C@hxgPNbhMqlrvvG5M0Wi<&XIZ(;sm~q_pa{8v zHnpslr!O^)$_yZ5&*9vAj!7mdD%9>vP4nCKl!EMnl8uVKSegEyW7P&DF=ma?GL{mLc%g6UpeArljgo)kA>gu62uKk zm3<3BN9_95?a=xChSD)2mjR4#1x9IF7(NH3>O(MnwS#rQdXeQ;>!(t0N-kAcS*zwX znuKyK*>AtI`SjwDXi*w6$-0*NeG$6+`q(4*$fl9Yg5%oV^QM{HQ}+H5_@z%hvpgRY zH&&<)`CU!NpHzW$=Or(%wx-MrM6 z=JNPHm!{+?sl$Ll&U#A~5;=+A!_1+n!o+d2Tv^7^Qb~x2=383z z0oGE{9b&0FaEW>UlSGn5;xpD|_PPKfew zh7~wsHdMo((%fl=UvokY)WrB2i)0(z;~#|7`Oa!(B#-+o zzYmmr0p{M0NQyFLzZ_WtwX^L*{hBIke=^^D!|xXUGe73)-teIPwhGj^lH^OGXHgLE zJV;+@jBb*+y=4<-W6)K8pfOS?+LLSAT|2=Vg5my?j(B~cfrLAj%EQrrc`;pszEhzY z-eWVjt&LffqTc3tW#|<0w}GQC>PQMsipuw^%R2qna~X>wUP05Po1;M_SA=O%)U$uz z$MWZ$UEYM5j!n1X^7o?97}?hlu@?O0yH{+(J{t8lke^d7I&pyDrz!_~y+y&-$^OWEm%OkI{z8KS2hc zETrVAC{4+5_$zg>-)n8>@VIUKgR!X|Htkl{{=y7@dEOq{56kw@M!@9(FuL2XtnhgF zd8=!Lm31t3Lq~Ay$dWBJE%qbK`f5!Xwv+a{#gv4Wir%}!JQxBwtMyvWKL2ZC>Rdo`rb2{qHpotT##yT!h8M+pm)8-nn1-=0CoqiP4 zDCiLU^#aprxOH6(mZIZd((Q_hOAhW#MCG zHO@w&)~FR7f&2FghP@xtV+XJ={FdrMAOA*pwWlo`s6W0TcW(h?!7f3dRNttku?7##k5H)`@EogfV44N&^kP_9AO=twn-;QA ztz_ar(vgu5DpC(Zi|YkoeEe92QiWW_8T|<;n|?YczyA95>+9>%le$^1LGYs1>-ZZu zTR{N^p;8rZpgs^mpE3H5Xl{6Y#6 zS66Lel4VZYE(t-arp&5U6>?5zSP=^4{~upp9TtVxtvjT&)KEhXLpRbOJ#@!7q;w4} zUD7=?2q+RmGqj|jbfbbah)7F=5-Q>yf8Te``JQ|3bN}FZV3^o@t#_}z*LvTz2XR*s z9{v3N`;@jFj4jeXWF`pK7(suE%2pL9TJg}$4pk5+SE~Wf>P>md&2bGfRd<;lxOd=#dl<;rS|-4&X&`(r0B9kKk%?e(d0{TEHxt-|T*DJtj~-?m)d zg+FWjcGUZ{7-Rml!f4T<=D|ycDfis=c@(Mvo>Og2KDh1X@zKCc;jv(4bDjm%vQ2ci z?;hpdfbQPja1E?xBqBRC2YUqwvyqvNEAPwf@(_P)S=(8}#`JBfL_RGoY16neS*;dm zIPO}_!9NL3(NLKaXY!b%ciliA1^G1VN@f3-sqDqz~7c1@C~iH5-2BycapEN<8^1joto(;tAX7JhIG<77CD z^Ce4NRl>o6Mg-dA5UkN?pK0<5c3WIMs@5P;@>;UrEf}zFkAU$?E3W1oGHtocBq!Mf zyfG6b3LwS_@x6k)&UBv&D9n`y=krGSYBmAC>WKe>;8$vrA34|zZi?BLBz2)TBnNug z5&~qYrQQzy{>`6Ask2eh7;2H%+*$k;ZF#X-IK;;109qMTSK-=gH#?`@x+o*Esjj@x z`t?4kQHx!EH?jIA+O?2TM_?@c?#u<=eZKgFR29Z)Vl2{KmqGdj^v-3OGH5w%gO18@ zVg@^io1DtFw|>$Z`HXuNA(>afJGk&TYw!ut6UJ=yLMP@|VFged4o8pR{J80r2NWTL znpD0{pirci)ngOEksXUd{a-f;BT{qH?2NnUZ~70eTSZXMf&3c})5_^JC-_RUzl~Cq z@ZNW8(`p|3=A8&%67QpVljN*U5&N6ofPiUZE~}eXD42kAuaoF2@(F0lkCk#PYS+_f z^wvcYMH!b*7Ef?2*ncsVrID z9GTdfFR0s25xxtYfi|h_p%#z#%5TBuI|dXZwCFX^Zo94hutfFz+%XIgqd~!?JR;Jp zHPc;wZ9O&6ZErd9jfru12Gfuj{l@Lp7cxOLiWsB|1#ebTJ`sL zL_g9;?|y5)m$#*muia@|S_rNx__dt>;eM(Q`j{f-;?ZTN9_hP<#O+@a#+%QZI`0OW z5)*PSTUml<1ZzxU zBDtd-0l$KGpR%&7z&B4q>=(? zt$tn3A0Tv+;D4Ir z?;2L6ZX7cvb}9C)&EIQC?58>zTB0Vy5+*|kDr_LUad|1#zho8tc?&+q^lsP;nBM0y z1Z767%Et9}SXZSfO61rq+_;SNrApLklqBauELKH@ZLcDn%g_3wni*LkFkT+T)yzSQ zH}l^f2ed7+jCC{eBFmp+S?3HsZc#)~QP=e{N+#?v8Kwb$SI%+!2v?&&b){bcTcoi( z=VkH@@icHLCdkqXWe!Dx6L#M-+C0ZTqWJ@07*RY4;2lFpU^izB>GhpZ7moI`7bQ0c zo!!3I{1W4vM$RzO&$E_Bw?C``qVQ%KcpkbRP4Z5O=@JN^ap} ziJVDJW*f5__`LOZChdyc(FC3BI?0I=t>h)4)AncE**3Vfnlc<33s+M|)M2`IrJ0ev znS-FvS9Yby#7?p=y1kc4$vUrnr4Wx_t4R=~?Rk@iXp>9@kdb5iZQ7+~V#bUydVV>p zWvutY4UCt&w}W`gs626%iB9z8CST=KhJCfv2s(0irCbCE3(ER{kfyI_w@Q=38)YtR z>pM~&WiF;fOVo!ztfee)@(>-5eW^dggb7{F1XIJd{3W9A4YZ$F#S_y^*Hh`j3q_jP zk+rkj;Hoqz{tyZu@avU*)4_zhX}JMZ*wKc7b&cFLx;9#qvk%KUV=&z94*>sQ_J>}& zrtSPkLZX&ufM{}TzS&MMH1MmXIiI(hBWVr*WQWG$FEAA9 zEug8hxTJ`Mh8AcvG%qz7l0Zl;Yc4kX0^~B;u3TB(DGs5!_uQO2rQ`Hbd*ugz+fCCu zr!5AV$`s)%6w1*+Q#P``W#aw082=hw;8k&|A31;)GMAb!4grv+_SVBS9ouJ@7H^2bH4N#E-37LJP4o*(~ZHDzoBpEpx6w z<}>+o{0?aSa1!9h*zK~BjZ`vX-{0dEi`L>(B9g%(#yoHD5%kD@kQ@_ z0#`brOuEaO>o;{UzVVVDsjq5(czxX?nDHqtY~1eAO|pEeSpCTy_p(C-;oD#0r1*yv z6x7;ZvggEN0yC=t%CD#KMwH`YCi+B!_ z>9FQ1zBZld#^e|yA@4>t-Dm9+5%`6ca_2>%rU|w{$6-~$g{8xH)ZJCd)5q%S#wEF- zAC_iXz%=$tWxKtu4$huelBj^FLiUpd`9Za55%{AY?Z2br<|gQW=WMZTd@_H`GZU8+ zEl8lO1dM&Y&oH3-om6eiBUG4m5wif0X<&ndWrpdHwCksi&}mjxC{e9A0FTwdzr#g# z)TPm!hyH%Z^G9?&0wlfd-3*synHkqLZNgo#_Y^L4brqitZR$apva~D3_%({OB#&4}Cv+Hu6mS(0=udY> z7;N@LzHRDWiHoe5=RNZ}7MwtE@`(Nc$Q)WRiTN+seIvpjt3Y;Og$&xuM?XD=yE}i- z|6R*7pZOpgTusy4Mc7c#!*}IAT-;`2Je%C32Z`*Y;;l%>XUGo~AplW@PFrC_s%(<9WLO>uFUqWX#pb)M*b z7g=aXaklq%fa4U@syt6WMWnvdo!eA)ec|5LBj*4>K(>}t-KZzb6DC$tgG&zW$B!#R zXwt3KkLyNYD?Vy=Xo%}^O&-AthIq`l-06wkt z-^%gjN|b@^t+${$?Hw7J7tBgkHC07*brEb9QkrsjOig3>Ma4N%*U3xH8Oe;6s&04Z z!(wYe)7jk-0>o&7*<$WeM^W#TPh1Rz$B;1oH?ux+UMePrv5#H9AJMg4h`Ud1ts6vD z?Y=oiG$>>ua={%RfQN*0FE_g-Nci6vqg;}(PTTW(f*dzS+uCa89k^jTDsB%ill5!O z3z$Xw>ob))D_cjt%$@Lh^3CUoM;u-}(q;0`?-@vZJu)HGlLF(q_x{EN5(p`880q;0n!pI^ImnJl3YB1?F7^k6O%kr#ERpW!sB3cSix zN8)li*0#*5U2Xlc8lbwrulXNjDIc9@y<9mopF7uRkeypE{sWjMpju)VspwV>az4?z zOr^AVE0{2l0quYHbXXqrwfI~={--nMgm|__W0|U76V4tcv}=w~TXK5#nd~jmg7eUB zcIS&}6&RRY|J4g~uh|I0A5x!lUQ))*f~G!?w;neZ{u-{2p`uXGa9t5jd0IK&3bE$w zra7-K!~5on8CR*`Slsgr08g?u)mmVh%42S`#av4dhX2IKqla?Q?Wq=@_Z!mCchrKq z#N~-&pQZ%NU;Yzaj6=@6FGAH*uE<@3ZqUmLKm8jo4OKAm>@4t@m$U@qe^- zx(hFMKJ%={SmVGu82@GE%YcUZn>Zd(rH}Jg}5^2PTB91rqc5@7_m=`n-OS;n)nCo|Lau8^G*yfU>**|6tv$7%Q#}lSC1Dx{u5mU&$bQ>Bi zqT(yHp(A&lk3aLW5@{&ZC3blJef;)U#EP7bs7%$jwtb(fu$m(d;$-}>J+}1(^TGmS zD#}>Pku@+fHefC}v1})QiR{7^6()ypde=cIZc8E&_S};WS?)^fs7E3D(Mkk=$Zc(4 zB)%HlbX8tDGB`na^*Grz&0_u2EAg>SjuTxcWNfqOV;^?I3Lc23X#zK&5%*AS6u-mL zns3^mF~1ASRl#Q6g&ggk98BR)WFgNpm~P`{zHc$o4Ltvd*>csWCkoasS{d|bbQW^% zXLJ4mgiHPw+vb0sb0MFGfdeMH43V+763eNAR8zd$GY@Oh_f!B&Rg#ojl6#VBF|Eaj z7<<=N{&4kTbmz!9G5NsX8EbYgVo>!(*@PtV`W(M~oJy#Ld0h0AnJln#QZ=v8(I^~+ z{?JkziNB!`!vp!U10Z$Cs_bP+`dL}QI~f~G4ozgkf5AV?J;2IA-t;B_%|8>9&?FeA zBpn}zE5d&ho+mq7mPfnRSBe&E71=T6+C=c#;dgE^424$(&)lJPP)(WDi>PJ=leu=s zL|RZm=SW6Rrsza6-Z;DAFH##H;+Ig%5eb0h%5GdHH7~M{`2Lhm|G0Cc8|$f7(w1$T zl~!>S`TS(tEjC^_W_i<4r6Nc$-|1W zuf6`s{cnuh)$j~Lr-icuf0wuay65ylZkCWmE{?pZ08~YgmVqMA5ZTE=Lv8XefsqWSdBaFJP`BA5zs65eDG1JjczVctA;^?F%FGQ_Qj| zlrXIKQ@#)$E@h)`ZdXGy?#INv7IO#mc&PQGP2=Znn-+k}_~QmNDk@a0%Nb5y{?!C@ z9Gl4B&JSPL&D`YLgh(i0^T4GBOKesWC8`-*IDNNSU$aZJ5TM0MI~*jdi$HNtoNz%5 z{dyBl5Ydq#hNLY|`gf0vs#J9@2CmqYAgD#uIjtR_#~r`>(hm8k2U(>;a841^KYnA; z(&Uxno~h;_e{QyRA=qlz4!Zv;k0t<=>x!+;uw%+n5M8vT31{bbSF+Yh%F^6%;vE>m z+f4ogfN>fwY0M#JmWpfLXfQ6jdYpx(ny{N@$8h-xRRt#DuJ-sqKsRvbW8_eVlP^J1$E;{mEH;`S%IUIggytg@ ztF0f(WQT}eFqy^|F$1I|{dnmmEiaM>ropLTY(rw=J-5M0W>4(p5B0~-)rbO&McDEr zSm*RX-tT<7bHxf!K8-=gKq@=d?V#bYs~lsQQ!BfshU$PxR^I5eVCxYxjb{5hG1d1F z(M(6&7maY9SrP6)ArNXMYzy!+G-)4feYp?P>j}mSd7?m}D zqXT(Psrz#ydHXOKEAS-;F8-3IBAS!lv`B?nqC{7*#lo;qdq;i@NJGCEV8pA36NM9} zCdn7RdJ5NAkXOq)8eJo3vpu`u;s%tKsR-7vdzq1?Ov)0YdHq*InUxG|QPxU!BTX3^ z#b@2(^3!_AnvAPC3aMPn7uWN7Iegme(l|DpO&?^~?1-&z?L&|hsj2PStERR#4(S{P zjZX@2ObkmkTL912?h7XV0dN=S=C6-cq6Vth$o~MIz{U6`&ekYS+;Sa|?ZORE&VOtI zb2sh>jbXj=X%!eNAGmgqIIoVSi#dRQV)-WgK?Yok*BBw(s}>E-;5USZzG+yAxzFH> zP0Y|g@^!+lyXhv2REt^SCZ;n@#ftdGW#4$9Vgh9VGE3V5#w3evi(0K7wL#;3Uh^>H zXACA{--1B#SGr(X6f0Jvr%0O$1Ko#e!U*G4gOOc>lD#+R-ud__JrEw`Fz@HGG^r9L z!CDhf2=EwO$&!GoUjSp4@$Vgx;8UDCq=e;f{C-oJz!fxpX-2o*} zcj&btE`>Mrzdy#-`ygwgdg_5C4%|oDN)0BV?_F)HM6TS0^~2&s*S6H3qR#3x+@FDu zop+e=(bRU#o07qi7m?8;P5b1y?Q^dK2`9pk%BCVulnFl(%x2u43k{1Ql29 z!xEP@lTvo20DHI?@z^)Z>=WvICr~oWksHhS(g7t-p+dv~D<*Zjlf)TMWaq`D1^=@6 zWH6RJb@dyZD?2V(!$0CtaP<;n$RW|l}Obdc_#=~ z6nrxyE|Pc8y#qW|-}%vpt4B}rv!kkmzZ!Q80@FfjyGJ$6HK|;Ye?dIgqA5aagUp3h z1SEEx&9; z*I~K8+p`n=nN5B=@guTEr_;&3gFQ9jiE%^><$xq(R9c1GzhV(ErpWu8G9xR2({M6k zC3Bv|(y+9dJ(M9B2LI5c=036TPxf$?>`Ei8{P08M+Lu=hSujd9_*pEKcPOBS(~z?C zZyES;khnY~RgY;SY^rkN7rKrA$RVTV3%y?d5}s_WOcw_M=`igM#u%+7HaIQ0Tg1NH zRbaaNEO$o6uY%yy1qtZmE;s4iu1%E?_w&wa>b!Q_JLtnH#7oNHo;u5$o`Ix$K~)-T zwtj+yPM|(CJYTC?oYTVbx^UmVj?}E^-H`eB*foJ(JCwk$YIBVjb%sQ>4>Z}9Qs6&2 zEj%3Qy_9EFcjy9=A^8v&ucZ=-GyEiq#7;ZOR?4!XPAP03>ZRt8!Eq{I8I92?T_Iw| zXMFO*L=t&xP@Ri2g!}QWj|C81oLPf)g;c_2?|cp8s2C3`>%)rZw=%nh`n$@BUH&cp zD!&BIXAdpP3*n%iQE~N&$9d##2b_{T>T|(9!wB4h$A9my-c<^R<}WRZgbl>?=VCGc z#<5LIN1HD#b(&n$GDt&G(<`i;6qrby3j#)&Uo(G%OWA?eo++#}Ls6a5*l~|rx@}SD z@h!_AVWaB*hEQ7d29((iM||d<&4ON3P1D8wR>PULvx(8(yq|ap+Zn~lF(?~5Jb#y4 zv2WmuWJad~*7->L;GK!4|HYy(2IEjY1qDM?j?3gmp&HsD)B)L0q)!5ST-13 zi)Q}ySloh+s@wXwhabO~pU8*BLbfebF8G3oVX9glM{y_h*53S3A=Zd?12xWzELDPt z+c9(P@Oni2)Nlq(YBPbd z<2s@N;u>02NI;2S`};ee1LqyiK6s+B?gfb{;*hdlXWEGQ@qDo|lhb{&c_t$>*mUwF z-;ll}b1rQ}y8pvk@Qx`?Gnt)>dy(a}`lvi`>alP`ft+91;<|wUnB11SypV81D+^M@ z%o43Jn9(MDpKn!UdxsMhrgHJj?p{BFkVJKwuA9{eQOO`P^-}!(vnkALKh^se4{-Z9 zM(O@C$X zm>h9}z@tjdS?rWouVMsnUTU^p^Ah3D zZ5#30N(gWBdj%p9SEQc`U3zNea?R1jFV}-@)hS?ONPpQ0x{D!% zYBDnoH?bNh{erKiX0fz1yQ8BAg0I?wXC1R7c%P0PiDT;}^2cVO@ed3#Vtl9rCE0Ud zVP>I{#K|J?%MhuS1u1b-U&nl2Qxavl^a))v=Y|nYme{Kai5$i^2exRlnYOyLmG5R4 za_QNlIN@an=%s-tb(~|a?@)qU<+l|?{u^5>(V+bwjXmav8NnlIVb->oHO62xtEO6u zdM02Yr$>j=bj9%9q2RblLqHLt>6^*fB&+vLgKn7By?o`8B31KwqV92S^6ZlAS;`8d zb<_DYx!t6%Ny;2C4dwts`U4A7a%gL4y@6u=P=)kZySPPXuZ_Af6@*gheyTduo@F>!ix8 zal^&d&bG=!CiE=Cj2hF0Urq?#E~_NvekdCC089B#?5~pAWQmKk(vZMEA^r^z@(yYe z<+SN^RndpBnUb~h`>n<8R@T%%irN4Y+}ozqc|&^lsE<|GLNSZz8_}W2&DVYVzvo?b z%r?_d5GM9-$nzv6?_Gp-qsEt4-(=X?SZUEM&v}wsBYpn>F1+zbKI&{4rF#PmUZHie zNT=3{NBUS($lWxBho0wJh=xWrYD^q<8L-_vUm(>nBGwFd|5UypL|OMb*(Hk|X+ZTV zj;5TCH-*2ZCbChR{B^3#?Hv719w)LAL4!4%W;Kr-`UB9VRAx2Mw`o&mBJAAe4UB>R z$nbQ$f5%4!Nj=||j>KMk6X(JqGYLhA9Gidd>bN6~aD%;cl)Nw47fP$rQeJ}n$fdd0 zUIc=QXKv(7aMTq2^vq?Vzm7IhfTV`?ohe!M!l)b-ByW@1t})#q6cQ=%L~2i z+*ZR_;t$4{b++4onKv8PjprS-H%*kfnK8;0jE8!5a3*R^&W`x@hQOmb2cPT$A^CYM z9Gx|1)TO4-NbEi=%aY`g&8L<5x$%%xs#mczGpR#|klRALNou#qqVj*jNvRq6yWWje z7xAx>(6#df1#(!ks2zW}ft0uyrZ_TD%MU11^yAeyra^mh{dCEqz>W^Zs|7=_CwW0q zn90!kq*7^p8>`%yY+@b;4yf>fUaKjR1ZF7JY-dboOCX3Hdh0!Z#%h8*?hP@FV)g^T zWlEkp0c;2$J~zvXoW#!CVQNb%`h){uSlA+VeIK83D92K*--4LkPQRs*cT+30ZP9f1 zI70qEtolM{Y#8XC*IrwwPMOQ~u+8ygCaH*NAev@jGsR?9DszzXYMZH0Ax_maMXx8o9$XTrECQSWWwR4B%Z1+ z<{smzpZvsylx;r+-=iidoIj4~F)nC_U@oGdgs{eb% zUo*D~{i8Fu`G{vAs40Qn{AFyHPwFPhZM0V8D=;HWqm5jN7T29!wK}ovXi7P1UDsYa zb4m@|yw7+;tGBx)5Zb^*jjni z%X*n*!)<#jWp`?C_U& zSr2sd*eD!9`_@VGns%92QmSdvN3z3D(Z1z1?h~fS2B9%N@)odoK_qx&ai>ue>NACR zTk_G0Nrh*ftQnraX{|i6cmhm$vH}hn89AMjgx!e0dY8%iL(iR$W_pDk0TY0*S~iR@ zZ`oTpJr-#!`ZZEMNKfQL((Ei9fuBtRAsiKfX6)P$jc^Y|=<^5jFHVgqGCYJ7Lu-e% zK7TUYP`DGus3cm>IK@RAY3K>&ZBZabkLiW!GmFCE&SQ~>cJ{eyYgO=522P3*0{TQQ zvlDcPSX#fSZ+i=ZF7fJ(BC%vY0+jLck(!|%8ZAh#+t0xCpWe8Sw4a9;Ut(Zlp2(UK zE%q02UC3R95ldcho|9apv8Zj%U7FCz>a?u~EqfQieg8w!pQ0WEJS1&w?u&LJzuPn7<{b1*Q{=+}t4Xw1;=C0&)>ASb zu{iMGF}5ymu4{ZkylAX1Ym!f5}HT`qyUof=Xti>I8)M;yiH$n^>CTf1HtQ=s2tQc>nhLr-_8GFH{92a2VrOW3Mi zx!R-9#9SX)SKr^vG#~SxiW0{=@wF^na!O2&)}Fwi+rzrY;@1d07+20X2QtMCfU^we z`6N9K623{+?*6i=ntH%7aa0vtnGsn+e9apLH@TPZU@#9F7IUyv~;3$7?L`71Xs(71#{ZnS@&0mca z%NFNIaCApYkG;xw#}v(@k}m2JOnnkB5<5f8mj`A%L8{?{D>C++ob1zgcaDr^96!G< z6jG<*^}xPGd7{Z;J*b8GQUVnq;%MJfI-$R8<)qJG;Ft%)bhHQasPJJE$n@j)!kZ~s zPhj8-@6H57$F0E*c8;iMu1x?r;>%)uLghd{I5{N=__ z_xV~08qwgIwo%U*zes-V6Iun=vOj>Fu>}mJkEba)KNCSu%txcsp<<2xAk5wt)c5s} zX9Mwa-MHj{qr&G(fYo8JG;h=vvFwNCN*);~$yOKtS5mOds^;Z_3<>#e_|e{ke-lRs;;v)OAFwjHwB|x^;UI=XIrC4Ev8px?kL>ROPQT{G~nBe(WMbS(k{E z1~!9^LM6CAlt@Pqsk}8^NT>1h^YWhJt4u@YnazMYi~OPtsd=mC0MtD$9BRf(qdW~= z_g@&rWAqkPIZUcXngA>3|Bv~Km@qN9!9Fn)#E zB+CNiBikHlY>ZW)6<=^T$8bkXzE7q9#_^d2$8QX~lKFpq37ch8AX#~6qCDD3yx&D( zQzo$kscxA=n{-eDFKL^IG|mn}jGbxaJwYM|Y+_{J=&#RpED;b0+@Yi|2zlqcjVCeH9tOwL}q; zUHH0?WuNEf@Z;a1g}t#y{pEJicNAr#RN@y@XfU$gQjjxn-P7ZAiH{1G-TQT$9fL8% zu^@S82~j|sRA1o8Y%cSe-~cmIE@Qi0_Rg-qGs6($h|&U#!S$>1vJfaEAz$45J8kzt5^nY9K4}hLF*8`#wD!|w>%xW9Hm9fApC)d6;%7@o{ z#N}aUUrY(jG)MrP+VKVkc3j76 zh9St(GrUJ^o!Yl$P(fD??^?e3WO1ccTg0MS_DHv@nE&|gF6KN1Qa5`xg)zOom9E?| z<}w@}UN2+otSF&z_Qa*o!U{?;6n{%&Eo~0zb%_^y>ZtEUp#`tpBb-d7wupDqxzA0H zBo`FOj25Q{1ZtJI1sTj}&Ci4Jq)c>_Y3!Sb=c@MuM9ByL5rbjivHy5x3R@#k26MUY zquWB*bUtEi^>8-G^7T|LANdwveDI>_@dXs(MXmWnyrtc~$#r|EDymG?<<<`GhyZ-~ zm0MdD&-6`;zN5hQK-Ta5a5wts=Q7`Nj)Cd1mWW3-sl6L>hx`{c%KM(pG1br0%o$x7 zhE5nO<1)#4(}rmnLM9gkz`0D9@+B&uHH>{~>vNNzM6V#lP0jd|iUZJt#s7JcC z}r1O9>DpqE$c5)`Mv zv}BqNdQO{ImT(ZI81R1%wbuZAeJTdIU z1#ow^xc-^BjSHg3A2u~}@4H^cX=d>cmt`DT9Ai8&=&;UVjIkEE@?%50fHr|l>QPz* zQ)RcyA$oKRgYk1jgVG)~X`aSppmp3o}SAlBMNfh81^zNv>z(3lJiZS|;wB|j-nf+^r zY#<-X$;xd?L!3g>ICiQk>qIYIy-WS2nla{?^YogAaM73A&Ej((-^~l%Gs|I{VHVv)=%;MANGv(+#7vAO$KzdQ{;-{LV0l14oq_{Vl;oK;vN7Z50{`JLR6>9 zcv@`q8Yas0jbpmDQl|HeNBUw6>2_egCRc4C(i5*5l%`%2o>ot2CHLoCSS8KS1+AIo z%-ohSLpv#nzUw{sL|mAD;nHPx+0o_7pMuVAOZ!&7q|yDnP^nWq_Dgo=CTol}vcQxJ zncp;NLr2x2WB~tEVsCpJ-wRCUx)XWE0_?RZ1nvZPMuw(!%Rx@0E;6r32fvB7b}~X# zcOX%SnnvjRh~dtg>XX3 zLeuS_&W%NsqxCYr(r?pbV`T5eQQF$-BC6OJX`6$@avwQP;9R)kn}NSZ&OErEST5y) zwMBFJ-Uhh8CqhW4_C&DlO>JU%&482fcpzs}J#V=VT{K?O!?rk=+)ab>3{Rz_Qg0|m z(jG+Ey%c#MNsgF#^102OM*wWIrDipSkxb4se$RWxHyW436ewDaYhTMW5_Zfxt4ptA+Fg8t`s8$+J*&$;hft%G080nAXQgfQ>j+6ll@tp;m z_%8Q1s&|(MRTr&_9+=u_mXm^N{vI`icF3u`v9RU>lD}JXt;Z5&N8#!HmmeLrgO)&Z zYSN}Jav#=Pr~9VMrB;a^;?;^~l{GoaJ+phdT!aZ-mSa8ozjH^=q-`m7Hd>D$>(`(| z(4cpz`q71j5a!~pvvd%nevSle4MCX|%PL{5eRuekAa^FYB^ym)gwD)TbX@X40a^$y zd?r_ZZ4#T^*HJENIV0TdnHwTF}6&rK!vs6uYwQk@b1EiI0wZI$zo&ryx~Wlhb152hCL@ zv}&PvegMKY@0;z>w*TtqN9z>*dh!4HWr!Zr`XKo2)6Xg4XR*HR0(lF-DEMFd@MCbwiax#$AgoyeCS(3l7{)%^R1c?SQx8GH_<%baXI2E`~(YQfS^Mzw?l z0{*0}Zz}HhdzdOR1uGARXP2slH2!wcEScTbxdH5F#Gk{r|XROu~4+Zi(CJO$A_ z(UT0A)}gB=V2Vz`qQd?&M=TN*`C`*ydwuAZg1luye+FRu4cVXmmffr2ceU& z2eFLR`{fLJWI@LR>c1CTl?j_IOMWID!(>@^%eb+dc||nub2%n z!vjuQskpcQM2xfN*&{3qi(nj(NJG7kA>Om}Or8KM)m=S*p0ZzL<%KT&&?f^)U}crdAoVtwBAAG|)!jMzeK#HmTDO zNR=keqw7O7PmefyxLE@w(j4aq*9NuYGBi?raoogN15F4B47#JZvfosgeDU4uj(=mZ z>J_fjP{Yu&jD5(}Qf`8zIU2PAQjSF4Ehq)ZA4m5>Ft^0j#VpB?@1i6R z30WI@*oNE~6{k=HCTc!=4!#d;F73CYwkqQ~Hl7gk8|j|BA+n=(Gi7d-_+lnJa{<8V z=xGbld>_Jg4g^eoR3Be|^<`#GvC(})Kp7&;OQjhw)YgW*M*}FjB`+c$+(PfUCFt{ zBTDG+avlH1A1p8a04_xv>Eo9^V7P7#b4`Z6r%%jR-P0$I-zq~|YFfuuX7s-FESA#+ z@Nxsec2v1m%EdnB5JYR~Wo8G1gd;LPFbZD&v!cSXpH}WfD4@Nk&N?gf7mpValv}sc zfrf8oAYZfz4-SgH&t0SZU~FR1U?uXnX1>hPR}l)L`AHi;lo>$+!aKHyH)k8E?lBT2Mvd?Y-48U%lNhzyE z)y&bpGEAz-<}Fp$AQCSsaaryUFHdo~jCBL`++Xi|&^bY9(|*qtcYzF^yFSfhIaY$; z+%mO+sd#v9=ZO&wO4(*S-*H3@1wB}8qr<{*Df*-j|4NAO6VjzgOy)Nh)L~3 zx(~Ny1;Yymz2hYmUj$fK-_G)3)>PM%^X108tIMR%x<9!Af|6rUO5tYlM23|1gYOO! z?OV^c+EW4ycX;K_%v#`qq zx+IU$KrXW*>KDLrg7dXpwl3&?M$0ZT{tRl{sY1slb=3a01_c@7$S``~j4Od=klb^ogpfAleW_URVm%b#}D_`#10%X*opPMx=!uM3TuYZ&<- zK>!g(AI<3rG!xj2Cr=tM*@830+oMe@X#9n%we$=zZyrDMTKso+XvnM*s3Vx+J9I!5 z&e*|+4yXa%zX!6-9h;gS;UBoRL);%Yv!v0J6=-#OTZwo@M%HbQnBB2il6$r0txD1y{#X1#wb?dQ%BmaQPN8Dio~x%3 zf}|5;i!7zkm;DBbK1AiUjn%(njdQakNJ!nx>|yPP$H0#ahe8|NFEaaIe!lQ^g{dWS zY9!5hg*F5C$7tKdD2kI;FyB>y@^FH+I~=j+d;m+0)IHDAYw`Pz$tL)Ug(4f!ci_L7 zYuR*C1~Z`oRe*tOGga^S-JUnlxv1}pfJ zs@dr(g7Y{8$KKRcuh!k@E%2u7ghQL|-7XE?=A&%E$1!M2j4gbj5C%3k|7bt8r)Qbp}$U*l51_0qx;BQzXr z03&?Xb?ttqz#S9FleHY?8ojchAUUsy^NIW6pz_3q-YG%8m+N8!+~mtU;GfsrJK`xr zVi^-z#5>L0URC$RvQ!m0znH99=h{W-4r#v)Wl$Dw>a^1vbzDMSj}j#lI$_#Y;GUGD zS1oHoT~K=|4!O)~^SofJH?YitH@J(irxgFa0zXv6`8%A8zfbAgU-2>efh6^uZ4KPx zL%DlV-4DgPy9|d{zrl+X{}0mDU8+;*x^rgi{6`%oXDAbzfEe3dzq0uQ2q|I%p_Dck z(RyH$du9Pn2LGFJ<=a+aNEi1IV)zxwrMTJ7tQdZXLfD$72kSEFv9NyN$0G^K;f4>O@~)0aF3S9LsSpUglloZnSewy}WyO zzLsOMd+hGko8-Sqc@nY4?wu-9=fgk=d!iW}wS*rv@|*XDVd*B&_=@3Q?7ePSkJ0B& zo*E*JXA^@iooyj{Zr@+b`Da^MsdxE5df4M)exCQRB1xg({bTNGbIi3CSZ19stot=; zYx#lDNR~I0G|mALGfZgtl>qoGXXeE*D>=Ws`2LLvGS%cknE7tvbg=oS!n!(z#D+Sa z8^IVqKa#2-+YlQeoU*Id)RMo_sqTLr`uvFT^T&E;eQG*yfwQuVN zmez1kcG#pNEr-7mea-%>ci3FCjCm*aZx(Wft-?QfD??%>tn5*$)4|ux+a#qBINoMl5jst0^I|99HCG#YJ zEmFQ@I~IQnC-`ZhL5r~TL48b-=+RV!0FFs2hxLc=gR#j*8(!)kK9>)5S2K2*>~hBa z0qCLECm2WKZs^gUk~i}SmhzWQ$OKBG%XpmZ$MBxkJ$O4SS?c63%CaZF}vFH+KE3kGn%K(%{_p&-xzyT~&Hd z**@UBmx;(iAdJVen~YX8Rz^E~sIP|=0pTY-Nab6SAxWDIm^GQ72<0jkgxn4C6O2Nd zDnT*;0F#-K>n2STHt|-aChE`MG2afh2P#rb5Cib@SoPV2R;aRG zpl#_t3(nGJJVJfJ4QscYK1UR?i)5~RihQKPfgztuqd3LyFCFj?syx2% ze6{F_65?zRjEKdEne@v>(%OV(jq)BqCW8+22$zC&3R|FUjYxab*1~ZoQ!+3)v#fy7 zh~TCU2W}}C8nWO;{P9(*h9V6Mpu{MBq7pzcr91y0LF+%2BE%E?=T|KkFqhqU?G&Wj zM6eQ?hOxKTQ@l>Bf+8O*1SCg5h}wf*h<=85mYW7Y`1HtTV#k$0GclL!Y|u(8o`1MS zW*1P%Q@}MLnnDa`_&0P7iFsd~i)`EeM)D2H>5y#_EJ0NgauX(bl^$M+bF+nO%$d=4 znn0EB`somm+wNV-__V5f87_Mr$iJ$e`j2c$X`7_OTGZXkQM9;^@G`xpHo&WJM{3kX zwbF$2;l!&`lJjOz;yQtN{J^t4vmPO%S6LDjpx4wy+IAckyiCniKg2rHOnBFS8X08w zPuv3z6YN20tjQVsJUc5jN_WzP6+JIpiN3HSUVl5jpozqwiLX+8;c=gF&)L{#)bk87 zRoTQEoWU6lLDXx1n+>%+P}{xd6tcg@#gx2>H3DBFWd3ig&pvyJw}sWV^#=e>dnM<% z?I>$9^B$j5DCq4qenxKB#x_p!X6lo&6rLQ)!F7TvjZ*m*Rf6etT{b$Ym6K8X%nLkX zQnE*}x%LtBtXX3ABtFp^26dCxSx?K_zHJ8oKC%|9gQC>lA&g#G z@}5O7f&}QOoFzUUfD8XrM}+D2nd7f5RrjvW(pW;kXz|PPeQ&| zWuP1uo4jGp0e&5MO|_7(pwvGAs#0wzD^?`LT@uX~4bX zOpknzKde5Gfz}Kw$qKiBf?lBKFl_my?!z$5e<1mP#;GY8I^Q@g>By|Zo4vlBao%u4 zH*ku3%@Hx(b=RI=Z1rTg;kQioNVce%ZZ-4z?@@;(Rix#D=iPfd%S@elo_)Vq#W8B0aZceQ;PhRn`GFWx~ zq|(QDxbGLYyp(yKYp`VE+*OAj^(iTybr<`$;i}B${|ry7`Y5tV4Sb?td%oCauz*eWXa(sgmvatvb8y2Q?a|N>9)>_-vW09obv(D7&)f1)VN57|o($%Wt+4GF&S6&n>b<^3FwCjAS z!)dK1M!n-1+d8IRY@HbqBNh4ZP4CzGfGpv&Hsn3ui9ctg>jCE`Ic`TS(oj*@dSmNb;8bM7F7Xc+FDwTh`yplzJnch75O}RI3fKU? W! Date: Thu, 17 Sep 2026 12:36:41 -0700 Subject: [PATCH 07/15] fix(knowledge): give document processing per-tenant queue lanes (#7937) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Document processing ran through one Trigger.dev queue with a global concurrency limit and no concurrency key, so a single connector backfill could hold every slot and leave every other tenant's uploads queued behind it for hours. Dispatch now names a lane — interactive for work a person is waiting on, backfill for connector-driven ingestion — and keys each run by the entity that owns the knowledge base, so the limit applies per tenant copy of the queue rather than fleet-wide. - Key on the owning entity, not the workspace: an organization-scoped knowledge base carries a null workspace id and would otherwise share one bucket with every other such tenant. Owned scopes reuse resourceScopeKey so tenant identity keeps one spelling. - Stamp the lane on the payload so quota and capacity continuations resume in the lane they were admitted against instead of promoting themselves out of the backfill ceiling. - Parse an absent or unrecognized lane as backfill rather than throwing, so payloads written before the lanes existed and payloads stamped by a newer version mid-rollout cannot burn a run's retry budget. - Keep interactive work on the pre-existing queue name; a queue the running worker has not registered parks its runs in PENDING_VERSION, and the app deploys separately from the worker. - Stop absorbing a rejected backfill chunk in-process: those documents are reported failed and reclaimed by the stuck-document sweep instead of outlasting the connector lease they run under. --- .../v1/knowledge/[id]/documents/route.test.ts | 3 +- .../background/knowledge-processing.test.ts | 8 +- apps/sim/background/knowledge-processing.ts | 44 +++- apps/sim/lib/core/config/env.ts | 3 +- apps/sim/lib/embeddings/client.ts | 19 +- ...bedding-processing-recovery.integration.ts | 9 +- .../ocr-input-failures.integration.ts | 9 +- ...rovider-processing-recovery.integration.ts | 3 +- .../connectors/sync-primitives.test.ts | 2 + .../knowledge/connectors/sync-primitives.ts | 17 +- .../document-processing-source.test.ts | 16 +- .../processing-continuation-dispatch.test.ts | 76 +++++++ .../processing-continuation-dispatch.ts | 8 + .../documents/processing-dispatch.ts | 3 +- .../documents/processing-lane.test.ts | 132 ++++++++++++ .../knowledge/documents/processing-lane.ts | 57 ++++++ .../documents/processing-outbox-event.ts | 6 + .../processing-outbox-handler.test.ts | 5 +- .../documents/processing-outbox-handler.ts | 3 + .../knowledge/documents/processing-payload.ts | 37 ++++ .../documents/processing-queue.test.ts | 188 ++++++++++++++++-- .../documents/processing-recovery.ts | 2 + apps/sim/lib/knowledge/documents/service.ts | 47 ++++- .../documents/storage-billing.test.ts | 1 + apps/sim/lib/knowledge/documents/types.ts | 10 +- 25 files changed, 640 insertions(+), 68 deletions(-) create mode 100644 apps/sim/lib/knowledge/documents/processing-continuation-dispatch.test.ts create mode 100644 apps/sim/lib/knowledge/documents/processing-lane.test.ts create mode 100644 apps/sim/lib/knowledge/documents/processing-lane.ts diff --git a/apps/sim/app/api/v1/knowledge/[id]/documents/route.test.ts b/apps/sim/app/api/v1/knowledge/[id]/documents/route.test.ts index 56d10d72095..39d4ab1c816 100644 --- a/apps/sim/app/api/v1/knowledge/[id]/documents/route.test.ts +++ b/apps/sim/app/api/v1/knowledge/[id]/documents/route.test.ts @@ -245,7 +245,8 @@ describe('v1 knowledge document upload route', () => { 'kb-1', {}, 'req-1', - SYSTEM_BILLING_ATTRIBUTION + SYSTEM_BILLING_ATTRIBUTION, + 'interactive' ) }) }) diff --git a/apps/sim/background/knowledge-processing.test.ts b/apps/sim/background/knowledge-processing.test.ts index 64f5f7c556f..2623409ec68 100644 --- a/apps/sim/background/knowledge-processing.test.ts +++ b/apps/sim/background/knowledge-processing.test.ts @@ -7,6 +7,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' const { mockAssertBillingAttributionSnapshot, mockProcessDocumentAsync, + mockQueue, mockResolveTriggerRegion, mockTask, mockTrigger, @@ -14,11 +15,16 @@ const { mockAssertBillingAttributionSnapshot: vi.fn(), mockProcessDocumentAsync: vi.fn(), mockResolveTriggerRegion: vi.fn(), + mockQueue: vi.fn((config) => config), mockTask: vi.fn((config) => config), mockTrigger: vi.fn(), })) -vi.mock('@trigger.dev/sdk', () => ({ task: mockTask, tasks: { trigger: mockTrigger } })) +vi.mock('@trigger.dev/sdk', () => ({ + queue: mockQueue, + task: mockTask, + tasks: { trigger: mockTrigger }, +})) vi.mock('@/lib/core/async-jobs/region', () => ({ resolveTriggerRegion: mockResolveTriggerRegion })) vi.mock('@/lib/billing/core/billing-attribution', () => ({ assertBillingAttributionSnapshot: mockAssertBillingAttributionSnapshot, diff --git a/apps/sim/background/knowledge-processing.ts b/apps/sim/background/knowledge-processing.ts index 7981c351208..8ca8526ae26 100644 --- a/apps/sim/background/knowledge-processing.ts +++ b/apps/sim/background/knowledge-processing.ts @@ -1,5 +1,5 @@ import { createLogger } from '@sim/logger' -import { task } from '@trigger.dev/sdk' +import { queue, task } from '@trigger.dev/sdk' import { env, envNumber } from '@/lib/core/config/env' import { BYOK_EMBEDDING_CREDENTIAL_REJECTION_MESSAGE, @@ -13,6 +13,10 @@ import { isPermanentDocumentProcessingError, isUsageLimitDocumentProcessingError, } from '@/lib/knowledge/documents/document-processing-error' +import { + BACKFILL_PROCESSING_QUEUE_NAME, + INTERACTIVE_PROCESSING_QUEUE_NAME, +} from '@/lib/knowledge/documents/processing-lane' import { assertDocumentProcessingBillingContext, assertDocumentProcessingPayload, @@ -205,6 +209,34 @@ export async function runDocumentProcessing( } } +/** + * Both lanes are keyed by tenant at dispatch, so `concurrencyLimit` is the + * ceiling one tenant may hold in that lane, not a ceiling for the fleet. The + * shared bound is the Trigger.dev environment concurrency limit, which is where + * a global ceiling belongs; observed peak there is ~97 across every task. + * + * Both default to the limit the single shared queue carried, which is what + * keeps this split from ever draining slower than the queue it replaces: the + * busiest case it has to beat is one tenant alone, and one tenant alone still + * gets the same slots it used to get for backfill plus a separate allowance for + * work someone is waiting on. Any second tenant is pure gain, because under the + * shared queue it got whatever the first one left. + * + * Splitting the two into separate variables is for operating them, not for + * sizing them: backfill is the one to lower when the environment ceiling is the + * binding constraint, and lowering it must not slow down a person's upload. + */ +export const interactiveProcessingQueue = queue({ + name: INTERACTIVE_PROCESSING_QUEUE_NAME, + concurrencyLimit: envNumber(env.KB_CONFIG_CONCURRENCY_LIMIT, 20), +}) + +/** Referenced by no dispatch site: named per trigger, declared here so the deploy registers it. */ +export const backfillProcessingQueue = queue({ + name: BACKFILL_PROCESSING_QUEUE_NAME, + concurrencyLimit: envNumber(env.KB_CONFIG_BACKFILL_CONCURRENCY_LIMIT, 20), +}) + export const processDocument = task({ id: 'knowledge-process-document', maxDuration: envNumber(env.KB_CONFIG_MAX_DURATION, 600), @@ -227,10 +259,12 @@ export const processDocument = task({ */ outOfMemory: { machine: 'large-2x' }, }, - queue: { - concurrencyLimit: envNumber(env.KB_CONFIG_CONCURRENCY_LIMIT, 20), - name: 'document-processing-queue', - }, + /** + * The lane every dispatch names explicitly. Declared here as well so a + * trigger that somehow omits the option still lands on a registered queue + * rather than waiting in `PENDING_VERSION` for one that does not exist. + */ + queue: interactiveProcessingQueue, run: (payload: DocumentProcessingPayload, { ctx }) => runDocumentProcessing(payload, ctx.attempt.number), }) diff --git a/apps/sim/lib/core/config/env.ts b/apps/sim/lib/core/config/env.ts index 2fcfd17badb..780a569bcf6 100644 --- a/apps/sim/lib/core/config/env.ts +++ b/apps/sim/lib/core/config/env.ts @@ -459,7 +459,8 @@ export const env = createEnv({ KB_CONFIG_RETRY_FACTOR: z.number().optional().default(2), // Retry backoff factor KB_CONFIG_MIN_TIMEOUT: z.number().optional().default(1000), // Min timeout in ms KB_CONFIG_MAX_TIMEOUT: z.number().optional().default(10000), // Max timeout in ms - KB_CONFIG_CONCURRENCY_LIMIT: z.number().optional().default(20), // Concurrent document-processing task runs (Trigger.dev queue depth) + KB_CONFIG_CONCURRENCY_LIMIT: z.number().optional().default(20), // Per-tenant concurrent document-processing runs in the interactive lane + KB_CONFIG_BACKFILL_CONCURRENCY_LIMIT: z.number().optional().default(20), // Per-tenant concurrent document-processing runs in the connector-backfill lane KB_CONFIG_EMBEDDING_CONCURRENCY: z.number().optional().default(8), // Concurrent embedding API requests within one embed call /** Deployment operating budgets shared by every caller using the same provider credential. */ KB_CONFIG_EMBEDDING_REQUESTS_PER_MINUTE: z.number().positive().optional().default(600), diff --git a/apps/sim/lib/embeddings/client.ts b/apps/sim/lib/embeddings/client.ts index 846ba759f0d..aea273979a7 100644 --- a/apps/sim/lib/embeddings/client.ts +++ b/apps/sim/lib/embeddings/client.ts @@ -70,11 +70,20 @@ const logger = createLogger('EmbeddingClient') * Embedding requests issued concurrently within a single embed call. * * A provider's rate limit is per API key, so this multiplies with however many - * documents are being processed at once: the document-processing queue admits - * {@link env.KB_CONFIG_CONCURRENCY_LIMIT} task runs, each reaching here. It was - * previously read from that same variable, so one knob set both factors and the - * product reached four figures of in-flight requests against one key — enough to - * hold a provider at its limit indefinitely, which no retry policy can absorb. + * documents are being processed at once. That document count is no longer a + * single number: the processing queues admit + * {@link env.KB_CONFIG_CONCURRENCY_LIMIT} interactive and + * {@link env.KB_CONFIG_BACKFILL_CONCURRENCY_LIMIT} backfill runs *per tenant*, + * bounded in aggregate by the Trigger.dev environment concurrency limit, and + * each run reaches here. The product is held down instead by the durable + * per-credential token bucket in `waitForProviderAdmission`, which every one of + * those runs shares. This factor was previously read from the same variable as + * the queue depth, so one knob set both and the product reached four figures of + * in-flight requests against one key — enough to hold a provider at its limit + * indefinitely, which no retry policy can absorb. + * + * The `bulk` parameter below is a different axis: it marks document indexing as + * opposed to query-time embedding, and is true for an interactive upload too. */ const DEFAULT_CONCURRENT_BATCHES = 8 const MAX_ALLOWED_CONCURRENT_BATCHES = 16 diff --git a/apps/sim/lib/knowledge/__integration__/embedding-processing-recovery.integration.ts b/apps/sim/lib/knowledge/__integration__/embedding-processing-recovery.integration.ts index 141d16eeaa6..e23caca3fde 100644 --- a/apps/sim/lib/knowledge/__integration__/embedding-processing-recovery.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/embedding-processing-recovery.integration.ts @@ -174,7 +174,14 @@ describe('embedding progress survives a processing slice', () => { }) }) expect( - await processDocumentsWithQueue([file], ids.knowledgeBaseId, {}, requestId, billing) + await processDocumentsWithQueue( + [file], + ids.knowledgeBaseId, + {}, + requestId, + billing, + 'interactive' + ) ).toMatchObject({ accepted: 1, failed: 0 }) const [deferred] = await db.select().from(document).where(eq(document.id, file.documentId)) expect(deferred).toMatchObject({ diff --git a/apps/sim/lib/knowledge/__integration__/ocr-input-failures.integration.ts b/apps/sim/lib/knowledge/__integration__/ocr-input-failures.integration.ts index 477fb7d5253..3dd20af7932 100644 --- a/apps/sim/lib/knowledge/__integration__/ocr-input-failures.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/ocr-input-failures.integration.ts @@ -168,7 +168,14 @@ describe('OCR input failures stop without partial indexing or futile retries', ( workspaceId: ids.workspaceId, }) expect( - await processDocumentsWithQueue([file], ids.knowledgeBaseId, {}, generateId(), billing) + await processDocumentsWithQueue( + [file], + ids.knowledgeBaseId, + {}, + generateId(), + billing, + 'interactive' + ) ).toMatchObject({ accepted: 1, failed: 0 }) const [failed] = await db.select().from(document).where(eq(document.id, file.documentId)) expect(failed).toMatchObject({ diff --git a/apps/sim/lib/knowledge/__integration__/provider-processing-recovery.integration.ts b/apps/sim/lib/knowledge/__integration__/provider-processing-recovery.integration.ts index 98831b8765b..c653e69eac0 100644 --- a/apps/sim/lib/knowledge/__integration__/provider-processing-recovery.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/provider-processing-recovery.integration.ts @@ -292,7 +292,8 @@ describe('provider throttling resumes the shared indexing pipeline', () => { ids.knowledgeBaseId, {}, requestId, - billing + billing, + 'interactive' ) if (holdParentHandoff) { await Promise.race([ diff --git a/apps/sim/lib/knowledge/connectors/sync-primitives.test.ts b/apps/sim/lib/knowledge/connectors/sync-primitives.test.ts index 6ae03568626..4c8d403b02c 100644 --- a/apps/sim/lib/knowledge/connectors/sync-primitives.test.ts +++ b/apps/sim/lib/knowledge/connectors/sync-primitives.test.ts @@ -383,6 +383,7 @@ describe('processDocOps dispatch buffering', () => { {}, expect.any(String), input.billingAttribution, + 'backfill', { connectorId: 'connector', stillHeld: input.lease.stillHeld } ) expect(input.state.result).toMatchObject({ docsAdded: 1, docsUpdated: 1 }) @@ -405,6 +406,7 @@ describe('processDocOps dispatch buffering', () => { {}, expect.any(String), input.billingAttribution, + 'backfill', { connectorId: 'connector', stillHeld: input.lease.stillHeld } ) expect(input.state.result.processingDispatch).toEqual({ requested: 3, accepted: 0, failed: 0 }) diff --git a/apps/sim/lib/knowledge/connectors/sync-primitives.ts b/apps/sim/lib/knowledge/connectors/sync-primitives.ts index 719144ae011..5f20e765fca 100644 --- a/apps/sim/lib/knowledge/connectors/sync-primitives.ts +++ b/apps/sim/lib/knowledge/connectors/sync-primitives.ts @@ -11,7 +11,6 @@ import { toError } from '@sim/utils/errors' import { generateId } from '@sim/utils/id' import { and, asc, desc, eq, inArray, isNotNull, isNull, lt, ne, sql } from 'drizzle-orm' import type { BillingAttributionSnapshot } from '@/lib/billing/core/billing-attribution' -import { env, envNumber } from '@/lib/core/config/env' import { ProviderCapacityDeferredError } from '@/lib/core/rate-limiter/provider-capacity-error' import { withDatabaseReadRetry } from '@/lib/db/read-retry' import type { ConnectorAccessMode } from '@/lib/knowledge/connectors/access-modes' @@ -81,20 +80,12 @@ export const CONNECTOR_SYNC_MAX_SOURCE_PAYLOAD_BYTES = 256 * 1024 * 1024 const PROCESSING_DISPATCH_BATCH_SIZE = 25 /** - * Bounds each sync's contribution to the shared processing queue. Oldest eligible - * documents drain first; the remaining backlog stays eligible for subsequent syncs. + * Bounds each sync's contribution to this tenant's bulk processing queue. Oldest + * eligible documents drain first; the remaining backlog stays eligible for + * subsequent syncs. */ export const STUCK_RETRY_MAX_CANDIDATES_PER_SYNC = 200 -/** - * Concurrent `knowledge-process-document` runs, shared by every workspace. - * - * Read from the same env var the task itself is configured with rather than - * restated, so the drain estimate below cannot describe a queue depth the - * deployment does not actually run. - */ -const PROCESSING_QUEUE_CONCURRENCY = envNumber(env.KB_CONFIG_CONCURRENCY_LIMIT, 20) - export class ConnectorSyncCapacityError extends Error {} export function sourcePageFitsSyncWorkingSet(rowsAlreadyLoaded: number, pageRows: number): boolean { @@ -1012,6 +1003,7 @@ export async function processDocOps(input: ProcessDocOpsInput): Promise {}, generateId(), billingAttribution, + 'backfill', { connectorId, stillHeld: input.lease.stillHeld } ) result.processingDispatch.accepted += dispatch.accepted @@ -1510,6 +1502,7 @@ export async function sweepStuckDocuments(input: SweepStuckDocumentsInput): Prom {}, generateId(), billingAttribution, + 'backfill', { connectorId, stillHeld: input.lease.stillHeld } ) result.processingDispatch.accepted += dispatch.accepted diff --git a/apps/sim/lib/knowledge/documents/document-processing-source.test.ts b/apps/sim/lib/knowledge/documents/document-processing-source.test.ts index dd0941ad827..1be1439f8f8 100644 --- a/apps/sim/lib/knowledge/documents/document-processing-source.test.ts +++ b/apps/sim/lib/knowledge/documents/document-processing-source.test.ts @@ -1599,7 +1599,8 @@ describe('in-process quota continuation dispatch', () => { 'knowledge-base-1', {}, 'request-1', - BILLING_ATTRIBUTION + BILLING_ATTRIBUTION, + 'interactive' ) ).resolves.toEqual({ requested: 1, accepted: 1, failed: 0, failedDocumentIds: [] }) @@ -1648,6 +1649,7 @@ describe('in-process quota continuation dispatch', () => { {}, 'request-1', BILLING_ATTRIBUTION, + 'interactive', undefined, context ) @@ -1671,7 +1673,8 @@ describe('in-process quota continuation dispatch', () => { 'knowledge-base-1', {}, 'request-1', - BILLING_ATTRIBUTION + BILLING_ATTRIBUTION, + 'interactive' ) ).resolves.toEqual({ requested: 1, accepted: 1, failed: 0, failedDocumentIds: [] }) @@ -1695,7 +1698,8 @@ describe('in-process quota continuation dispatch', () => { 'knowledge-base-1', {}, 'request-1', - BILLING_ATTRIBUTION + BILLING_ATTRIBUTION, + 'interactive' ) ).resolves.toMatchObject({ accepted: 1, failed: 0 }) expect(mockTrigger).not.toHaveBeenCalled() @@ -1876,7 +1880,8 @@ describe('in-process quota continuation dispatch', () => { 'knowledge-base-1', {}, 'request-1', - BILLING_ATTRIBUTION + BILLING_ATTRIBUTION, + 'interactive' ) ).resolves.toEqual({ requested: 1, accepted: 1, failed: 0, failedDocumentIds: [] }) @@ -1901,7 +1906,8 @@ describe('in-process quota continuation dispatch', () => { 'knowledge-base-1', {}, 'request-1', - BILLING_ATTRIBUTION + BILLING_ATTRIBUTION, + 'interactive' ) ).resolves.toEqual({ requested: 1, accepted: 1, failed: 0, failedDocumentIds: [] }) diff --git a/apps/sim/lib/knowledge/documents/processing-continuation-dispatch.test.ts b/apps/sim/lib/knowledge/documents/processing-continuation-dispatch.test.ts new file mode 100644 index 00000000000..83abbbad122 --- /dev/null +++ b/apps/sim/lib/knowledge/documents/processing-continuation-dispatch.test.ts @@ -0,0 +1,76 @@ +/** + * @vitest-environment node + */ +import { beforeEach, describe, expect, it, vi } from 'vitest' + +const { mockTrigger, mockResolveTriggerRegion } = vi.hoisted(() => ({ + mockTrigger: vi.fn(), + mockResolveTriggerRegion: vi.fn().mockResolvedValue('us-east-1'), +})) + +vi.mock('@trigger.dev/sdk', () => ({ tasks: { trigger: mockTrigger } })) +vi.mock('@/lib/core/async-jobs/region', () => ({ + resolveTriggerRegion: mockResolveTriggerRegion, +})) + +import type { BillingAttributionSnapshot } from '@/lib/billing/core/billing-attribution' +import { dispatchDocumentProcessingContinuation } from '@/lib/knowledge/documents/processing-continuation-dispatch' +import type { + DocumentProcessingLane, + DocumentProcessingPayload, +} from '@/lib/knowledge/documents/processing-payload' + +const BILLING_ATTRIBUTION = { + actorUserId: 'user-1', + workspaceId: null, + organizationId: 'org-1', + billedAccountUserId: 'owner-1', + billingEntity: { type: 'organization', id: 'org-1' }, + billingPeriod: { start: '2026-09-01T00:00:00.000Z', end: '2026-10-01T00:00:00.000Z' }, + payerSubscription: null, +} satisfies BillingAttributionSnapshot + +function payload(lane: DocumentProcessingLane): DocumentProcessingPayload { + return { + knowledgeBaseId: 'knowledge-base-1', + documentId: 'document-1', + processingLane: lane, + docData: { filename: 'a.txt', fileUrl: '/a.txt', fileSize: 1, mimeType: 'text/plain' }, + processingOptions: {}, + requestId: 'request-1', + billingScope: 'organization', + actorUserId: 'user-1', + workspaceId: null, + organizationId: 'org-1', + billingAttribution: BILLING_ATTRIBUTION, + } +} + +describe('dispatchDocumentProcessingContinuation', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + /** + * A deferred backfill document resuming as interactive work would be a way + * around the tenant's backfill ceiling: every quota or capacity deferral + * would promote one more run into the lane a person's upload is waiting in. + */ + it.each([ + ['backfill', 'document-processing-backfill-queue'], + ['interactive', 'document-processing-queue'], + ] as const)('resumes a %s document in the same lane', async (lane, expectedQueue) => { + await dispatchDocumentProcessingContinuation( + payload(lane), + new Date('2026-09-17T19:00:00.000Z'), + 'continuation-key', + true + ) + + expect(mockTrigger).toHaveBeenCalledTimes(1) + expect(mockTrigger.mock.calls[0][2]).toMatchObject({ + queue: expectedQueue, + concurrencyKey: 'organization:org-1', + }) + }) +}) diff --git a/apps/sim/lib/knowledge/documents/processing-continuation-dispatch.ts b/apps/sim/lib/knowledge/documents/processing-continuation-dispatch.ts index 99a228cf5d5..74335ad8d5c 100644 --- a/apps/sim/lib/knowledge/documents/processing-continuation-dispatch.ts +++ b/apps/sim/lib/knowledge/documents/processing-continuation-dispatch.ts @@ -5,6 +5,7 @@ import { resolveTriggerRegion } from '@/lib/core/async-jobs/region' import { env } from '@/lib/core/config/env' import { isTriggerDevEnabled } from '@/lib/core/config/env-flags' import { isInsideTriggerRun } from '@/lib/core/config/trigger-runtime' +import { documentProcessingQueueOptions } from '@/lib/knowledge/documents/processing-lane' import type { DocumentProcessingPayload } from '@/lib/knowledge/documents/processing-payload' export interface DocumentProcessingContinuation { @@ -30,6 +31,13 @@ export async function dispatchDocumentProcessingContinuation( delay: deferredUntil, idempotencyKey, tags: [`knowledgeBaseId:${payload.knowledgeBaseId}`, `documentId:${payload.documentId}`], + /** + * A continuation stays in the lane its original dispatch was admitted + * against. Re-deriving the lane here would let a backfill document that + * deferred on quota resume as interactive work and escape the tenant's + * bulk ceiling — the retry path would become the way around the limit. + */ + ...documentProcessingQueueOptions(payload), region, }) return diff --git a/apps/sim/lib/knowledge/documents/processing-dispatch.ts b/apps/sim/lib/knowledge/documents/processing-dispatch.ts index 4f3fb6f235e..6b67d8ce81d 100644 --- a/apps/sim/lib/knowledge/documents/processing-dispatch.ts +++ b/apps/sim/lib/knowledge/documents/processing-dispatch.ts @@ -77,7 +77,8 @@ export async function dispatchDocumentProcessing({ knowledgeBaseId, processingOptions, requestId, - billingAttribution + billingAttribution, + 'interactive' ) failedDocumentIds = dispatch.failedDocumentIds failureMessage = 'Document processing dispatch was not accepted' diff --git a/apps/sim/lib/knowledge/documents/processing-lane.test.ts b/apps/sim/lib/knowledge/documents/processing-lane.test.ts new file mode 100644 index 00000000000..24178163dd4 --- /dev/null +++ b/apps/sim/lib/knowledge/documents/processing-lane.test.ts @@ -0,0 +1,132 @@ +/** + * @vitest-environment node + */ +import { describe, expect, it } from 'vitest' +import { + BACKFILL_PROCESSING_QUEUE_NAME, + documentProcessingQueueOptions, + INTERACTIVE_PROCESSING_QUEUE_NAME, +} from '@/lib/knowledge/documents/processing-lane' +import type { DocumentProcessingPayload } from '@/lib/knowledge/documents/processing-payload' +import { resolveDocumentProcessingLane } from '@/lib/knowledge/documents/processing-payload' + +const BILLING_ATTRIBUTION = { + actorUserId: 'user-1', + workspaceId: 'workspace-1', + organizationId: null, + billedAccountUserId: 'owner-1', + billingEntity: { type: 'user' as const, id: 'owner-1' }, + billingPeriod: { start: '2026-09-01T00:00:00.000Z', end: '2026-10-01T00:00:00.000Z' }, + payerSubscription: null, +} + +const BASE = { + knowledgeBaseId: 'knowledge-base-1', + documentId: 'document-1', + processingLane: 'interactive', + docData: { filename: 'a.txt', fileUrl: '/a.txt', fileSize: 1, mimeType: 'text/plain' }, + processingOptions: {}, + requestId: 'request-1', +} satisfies Partial + +function workspacePayload( + overrides: Partial = {} +): DocumentProcessingPayload { + return { + ...BASE, + billingScope: 'workspace', + actorUserId: 'user-1', + workspaceId: 'workspace-1', + billingAttribution: BILLING_ATTRIBUTION, + ...overrides, + } +} + +function organizationPayload(organizationId: string): DocumentProcessingPayload { + return { + ...BASE, + processingLane: 'backfill', + billingScope: 'organization', + actorUserId: 'user-1', + workspaceId: null, + organizationId, + billingAttribution: { ...BILLING_ATTRIBUTION, workspaceId: null, organizationId }, + } +} + +describe('resolveDocumentProcessingLane', () => { + it('keeps an explicit interactive stamp', () => { + expect(resolveDocumentProcessingLane('interactive')).toBe('interactive') + }) + + /** + * Payloads written before the lanes existed carry no lane, and a rolling + * deploy can hand this version one stamped by a newer. Neither may throw: + * failing the run would burn its retry budget for the length of a rollout. + */ + it.each([undefined, null, '', 'backfill', 'express', 42, {}, 'Interactive', ' interactive'])( + 'reads %p as backfill rather than throwing or widening', + (value) => { + expect(resolveDocumentProcessingLane(value)).toBe('backfill') + } + ) +}) + +describe('documentProcessingQueueOptions', () => { + it('routes interactive work to the interactive queue keyed by workspace', () => { + expect(documentProcessingQueueOptions(workspacePayload())).toEqual({ + queue: INTERACTIVE_PROCESSING_QUEUE_NAME, + concurrencyKey: 'workspace:workspace-1', + }) + }) + + it('routes backfill to its own queue under the same key', () => { + expect( + documentProcessingQueueOptions(workspacePayload({ processingLane: 'backfill' })) + ).toEqual({ + queue: BACKFILL_PROCESSING_QUEUE_NAME, + concurrencyKey: 'workspace:workspace-1', + }) + }) + + /** + * The incident this split exists for: organization-scoped knowledge bases + * carry `workspaceId: null`, so a workspace-only key would collapse every one + * of them onto a single shared queue and starve the rest of the fleet again. + */ + it('keys an organization-owned knowledge base on its organization', () => { + expect(documentProcessingQueueOptions(organizationPayload('org-1')).concurrencyKey).toBe( + 'organization:org-1' + ) + }) + + it('keys a knowledge base with no workspace or organization on its actor', () => { + const payload: DocumentProcessingPayload = { + ...BASE, + billingScope: 'non-workspace', + actorUserId: 'user-9', + workspaceId: null, + } + expect(documentProcessingQueueOptions(payload).concurrencyKey).toBe('user:user-9') + }) + + it('puts two tenants in the same lane on separate keys', () => { + const first = documentProcessingQueueOptions(organizationPayload('org-1')) + const second = documentProcessingQueueOptions(organizationPayload('org-2')) + expect(first.queue).toBe(second.queue) + expect(first.concurrencyKey).not.toBe(second.concurrencyKey) + }) + + it('keeps an organization and a workspace sharing an id on separate keys', () => { + expect( + documentProcessingQueueOptions(organizationPayload('shared-id')).concurrencyKey + ).not.toBe( + documentProcessingQueueOptions( + workspacePayload({ + workspaceId: 'shared-id', + billingAttribution: { ...BILLING_ATTRIBUTION, workspaceId: 'shared-id' }, + }) + ).concurrencyKey + ) + }) +}) diff --git a/apps/sim/lib/knowledge/documents/processing-lane.ts b/apps/sim/lib/knowledge/documents/processing-lane.ts new file mode 100644 index 00000000000..9a64f51113a --- /dev/null +++ b/apps/sim/lib/knowledge/documents/processing-lane.ts @@ -0,0 +1,57 @@ +import { resourceScopeFromOwner, resourceScopeKey } from '@/lib/core/resource-scope' +import type { + DocumentProcessingBillingContext, + DocumentProcessingPayload, +} from '@/lib/knowledge/documents/processing-payload' + +/** + * Queue backing the interactive lane. + * + * Deliberately the name the single shared queue already used in every deployed + * environment. A queue named at trigger time that the running Trigger.dev + * version has not registered leaves its run in `PENDING_VERSION` until the + * worker deploy catches up, and the app and worker deploy separately. Keeping + * person-facing work on the pre-existing name means only the backfill lane can + * be caught by that window, and stranded backfill work is exactly what the + * stuck-document sweep already recovers. + */ +export const INTERACTIVE_PROCESSING_QUEUE_NAME = 'document-processing-queue' + +export const BACKFILL_PROCESSING_QUEUE_NAME = 'document-processing-backfill-queue' + +/** + * The fairness key a lane's concurrency limit is applied per copy of. + * + * Keyed on the entity that owns the knowledge base, which is the entity whose + * sync can produce unbounded work — not on the workspace alone. An + * organization-scoped knowledge base carries `workspaceId: null`, so keying on + * the workspace would collapse every such tenant onto one shared bucket and + * reproduce the starvation the lanes exist to prevent. + * + * Owned scopes defer to {@link resourceScopeKey} so tenant identity has one + * spelling across the codebase. A knowledge base with neither owner has no + * `ResourceScope`, and falls back to the actor that created it. + */ +function documentProcessingTenantKey(context: DocumentProcessingBillingContext): string { + return context.billingScope === 'non-workspace' + ? `user:${context.actorUserId}` + : resourceScopeKey(resourceScopeFromOwner(context)) +} + +/** + * Trigger.dev options placing one payload in its lane's per-tenant queue copy. + * Both must be set together: the queue name alone is shared by every tenant, + * and the key alone would split whichever queue the payload happened to land in. + */ +export function documentProcessingQueueOptions(payload: DocumentProcessingPayload): { + queue: string + concurrencyKey: string +} { + return { + queue: + payload.processingLane === 'interactive' + ? INTERACTIVE_PROCESSING_QUEUE_NAME + : BACKFILL_PROCESSING_QUEUE_NAME, + concurrencyKey: documentProcessingTenantKey(payload), + } +} diff --git a/apps/sim/lib/knowledge/documents/processing-outbox-event.ts b/apps/sim/lib/knowledge/documents/processing-outbox-event.ts index ae893680044..ea8d2071ae1 100644 --- a/apps/sim/lib/knowledge/documents/processing-outbox-event.ts +++ b/apps/sim/lib/knowledge/documents/processing-outbox-event.ts @@ -1,6 +1,7 @@ import type { db } from '@sim/db' import type { BillingAttributionSnapshot } from '@/lib/billing/core/billing-attribution' import { enqueueOutboxEvent } from '@/lib/core/outbox/service' +import type { DocumentProcessingLane } from '@/lib/knowledge/documents/processing-payload' import type { ProcessingOptions } from '@/lib/knowledge/documents/service' export const KNOWLEDGE_DOCUMENT_PROCESSING_OUTBOX_EVENT = 'knowledge.document.processing.dispatch' @@ -10,6 +11,11 @@ export interface KnowledgeDocumentProcessingOutboxPayload { documentId: string processingOptions: ProcessingOptions billingAttribution: BillingAttributionSnapshot + /** + * Required so the relay never has to infer a lane. Rows enqueued before the + * lanes existed carry none and are replayed conservatively as bulk. + */ + processingLane: DocumentProcessingLane } /** Enqueues durable processing in the same transaction that creates the document. */ diff --git a/apps/sim/lib/knowledge/documents/processing-outbox-handler.test.ts b/apps/sim/lib/knowledge/documents/processing-outbox-handler.test.ts index 4ea7dc6b006..22df7231552 100644 --- a/apps/sim/lib/knowledge/documents/processing-outbox-handler.test.ts +++ b/apps/sim/lib/knowledge/documents/processing-outbox-handler.test.ts @@ -56,6 +56,7 @@ const PAYLOAD = { documentId: 'document-1', processingOptions: { recipe: 'default', lang: 'en' }, billingAttribution: BILLING_ATTRIBUTION, + processingLane: 'interactive', } function createContext(eventId = 'outbox-event-1'): OutboxEventContext { @@ -120,7 +121,7 @@ describe('knowledge document processing outbox handler', () => { const context = { ...createContext(), deadlineAt: Date.now() + 550_000 } await handler()(PAYLOAD, context) expect(handler().timeoutMs).toBe(550_000) - expect(mocks.processDocumentsWithQueue.mock.calls[0][6]).toEqual({ + expect(mocks.processDocumentsWithQueue.mock.calls[0][7]).toEqual({ signal: context.signal, deadlineAt: context.deadlineAt, }) @@ -148,6 +149,7 @@ describe('knowledge document processing outbox handler', () => { { recipe: 'default', lang: 'en' }, 'outbox-event-stable', BILLING_ATTRIBUTION, + 'interactive', undefined, { signal: expect.any(AbortSignal), deadlineAt: undefined } ) @@ -215,6 +217,7 @@ describe('knowledge document processing outbox handler', () => { { recipe: 'default', lang: 'en' }, 'outbox-event-retry', BILLING_ATTRIBUTION, + 'interactive', undefined, { signal: expect.any(AbortSignal), deadlineAt: undefined } ) diff --git a/apps/sim/lib/knowledge/documents/processing-outbox-handler.ts b/apps/sim/lib/knowledge/documents/processing-outbox-handler.ts index eef0ceb2a25..112b3cef991 100644 --- a/apps/sim/lib/knowledge/documents/processing-outbox-handler.ts +++ b/apps/sim/lib/knowledge/documents/processing-outbox-handler.ts @@ -35,6 +35,7 @@ import { } from '@/lib/knowledge/documents/processing-outbox-event' import { assertDocumentProcessingPayload, + resolveDocumentProcessingLane, shouldRefundDocumentProcessingPredecessor, } from '@/lib/knowledge/documents/processing-payload' import { scheduleDocumentProcessingProviderContinuation } from '@/lib/knowledge/documents/processing-provider-continuation' @@ -100,6 +101,7 @@ function parsePayload(payload: unknown): KnowledgeDocumentProcessingOutboxPayloa documentId: requireNonEmptyString(record.documentId, 'documentId'), processingOptions: parseProcessingOptions(record.processingOptions), billingAttribution: assertBillingAttributionSnapshot(record.billingAttribution), + processingLane: resolveDocumentProcessingLane(record.processingLane), } } @@ -144,6 +146,7 @@ const processKnowledgeDocument: OutboxHandler = async (rawPayload, cont payload.processingOptions, context.eventId, payload.billingAttribution, + payload.processingLane, undefined, { signal: context.signal, deadlineAt: context.deadlineAt } ) diff --git a/apps/sim/lib/knowledge/documents/processing-payload.ts b/apps/sim/lib/knowledge/documents/processing-payload.ts index 83ccbe39878..8cd6a4a797b 100644 --- a/apps/sim/lib/knowledge/documents/processing-payload.ts +++ b/apps/sim/lib/knowledge/documents/processing-payload.ts @@ -3,10 +3,46 @@ import { assertBillingAttributionSnapshot, type BillingAttributionSnapshot, } from '@/lib/billing/core/billing-attribution' +/** + * Which shared processing queue a document's indexing pass is admitted through. + * + * `interactive` is work a person is waiting on — a direct upload, an upload + * session, a retry click, including a retry of a connector-owned document. + * `backfill` is connector-driven bulk ingestion, which is throughput-shaped + * rather than latency-shaped and is always reclaimable by the next sync's + * stuck-document sweep. + * + * The lanes exist because both used to share one queue: a single connector sync + * holding every slot left every other tenant's uploads waiting behind it. + * + * Unrelated to the `bulk` flag in `@/lib/embeddings/client`, which separates + * document indexing from query-time embedding. A person's upload is + * `interactive` here and `bulk` there, in the same request. + */ +export type DocumentProcessingLane = 'interactive' | 'backfill' + +/** + * Reads a lane off an untrusted payload, where only an explicit `interactive` + * stamp earns the interactive lane. + * + * Never throws, and never widens. Payloads written before the lanes existed + * carry no lane, and a rolling deploy can hand this version a payload stamped + * by a newer one; rejecting either would fail the run into its retry budget for + * the length of a rollout. Everything unrecognized reads as backfill, so an + * unlabeled pass cannot claim capacity it was not admitted against. + * + * Only payload parsers need this. A dispatch site names its lane as a required + * argument, so omitting one there is a compile error, not a silent downgrade. + */ +export function resolveDocumentProcessingLane(value: unknown): DocumentProcessingLane { + return value === 'interactive' ? 'interactive' : 'backfill' +} export interface DocumentProcessingPayloadBase { knowledgeBaseId: string documentId: string + /** Required so a new dispatch site cannot silently inherit interactive capacity. */ + processingLane: DocumentProcessingLane docData: { filename: string fileUrl: string @@ -371,6 +407,7 @@ export function assertDocumentProcessingPayload(value: unknown): DocumentProcess return { knowledgeBaseId: value.knowledgeBaseId, documentId: value.documentId, + processingLane: resolveDocumentProcessingLane(value.processingLane), docData: { filename: docData.filename, fileUrl: docData.fileUrl, diff --git a/apps/sim/lib/knowledge/documents/processing-queue.test.ts b/apps/sim/lib/knowledge/documents/processing-queue.test.ts index b4fdac5d664..64debf56f43 100644 --- a/apps/sim/lib/knowledge/documents/processing-queue.test.ts +++ b/apps/sim/lib/knowledge/documents/processing-queue.test.ts @@ -106,7 +106,8 @@ describe('processDocumentsWithQueue billing attribution', () => { 'knowledge-base-1', {}, 'request-1', - BILLING_ATTRIBUTION + BILLING_ATTRIBUTION, + 'interactive' ) const jobs = mockBatchTrigger.mock.calls[0][1] @@ -119,6 +120,7 @@ describe('processDocumentsWithQueue billing attribution', () => { expect(structuredClone(jobs[0].payload)).toEqual({ knowledgeBaseId: 'knowledge-base-1', documentId: 'document-1', + processingLane: 'interactive', docData: { filename: 'document.txt', fileUrl: 'https://example.com/document.txt', @@ -183,7 +185,14 @@ describe('processDocumentsWithQueue billing attribution', () => { ]) await expect( - processDocumentsWithQueue([DOCUMENT], 'knowledge-base-1', {}, 'request-1', undefined) + processDocumentsWithQueue( + [DOCUMENT], + 'knowledge-base-1', + {}, + 'request-1', + undefined, + 'interactive' + ) ).rejects.toThrow('Workspace document processing requires a billing attribution snapshot') expect(mockBatchTrigger).not.toHaveBeenCalled() const withdrawal = dbChainMockFns.set.mock.calls.find( @@ -204,7 +213,8 @@ describe('processDocumentsWithQueue billing attribution', () => { 'knowledge-base-1', {}, 'request-1', - BILLING_ATTRIBUTION + BILLING_ATTRIBUTION, + 'interactive' ) ).rejects.toThrow('Document processing workspace does not match billing attribution') expect(mockBatchTrigger).not.toHaveBeenCalled() @@ -216,7 +226,14 @@ describe('processDocumentsWithQueue billing attribution', () => { ]) await expect( - processDocumentsWithQueue([DOCUMENT], 'knowledge-base-1', {}, 'request-1', undefined) + processDocumentsWithQueue( + [DOCUMENT], + 'knowledge-base-1', + {}, + 'request-1', + undefined, + 'interactive' + ) ).rejects.toThrow('Document processing requires a workspace or organization owner') expect(mockBatchTrigger).not.toHaveBeenCalled() }) @@ -325,12 +342,114 @@ describe('processDocumentsWithQueue dispatch backend', () => { 'knowledge-base-1', {}, 'request-1', - BILLING_ATTRIBUTION + BILLING_ATTRIBUTION, + 'interactive' ) expect(mockBatchTrigger).toHaveBeenCalledTimes(1) }) + it('places each job in its lane queue under the owning tenant concurrency key', async () => { + markInsideTriggerRun() + + await processDocumentsWithQueue( + [DOCUMENT], + 'knowledge-base-1', + {}, + 'request-1', + BILLING_ATTRIBUTION, + 'interactive' + ) + + const items = mockBatchTrigger.mock.calls[0][1] + expect(items[0].options).toMatchObject({ + queue: 'document-processing-queue', + concurrencyKey: 'workspace:workspace-1', + }) + }) + + /** + * The starvation this split exists to prevent: connector backfill must not be + * able to occupy the queue a person's upload is admitted through. + */ + it('sends connector backfill to a different queue than interactive work', async () => { + markInsideTriggerRun() + + await processDocumentsWithQueue( + [DOCUMENT], + 'knowledge-base-1', + {}, + 'request-1', + BILLING_ATTRIBUTION, + 'backfill' + ) + + const items = mockBatchTrigger.mock.calls[0][1] + expect(items[0].options.queue).toBe('document-processing-backfill-queue') + expect(items[0].options.concurrencyKey).toBe('workspace:workspace-1') + }) + + it('keys an organization-owned knowledge base on its organization, not a null workspace', async () => { + markInsideTriggerRun() + dbChainMockFns.limit.mockResolvedValue([ + { userId: 'knowledge-owner', workspaceId: null, organizationId: 'org-1' }, + ]) + + await processDocumentsWithQueue( + [DOCUMENT], + 'knowledge-base-1', + {}, + 'request-1', + { + ...BILLING_ATTRIBUTION, + workspaceId: null, + organizationId: 'org-1', + billingEntity: { type: 'organization', id: 'org-1' }, + } satisfies BillingAttributionSnapshot, + 'backfill' + ) + + const items = mockBatchTrigger.mock.calls[0][1] + expect(items[0].options.concurrencyKey).toBe('organization:org-1') + }) + + /** + * A rejected backfill chunk means the tenant's own queue is already saturated. + * Absorbing those documents into the dispatching worker would turn queue + * backpressure into unbounded in-process fan-out on the one machine still + * holding the connector lease, so they are reported failed and left for the + * next sync's stuck-document sweep. The interactive lane still falls back, + * because nothing else would ever come back for a person's upload. + */ + it('leaves a rejected backfill chunk for the sweep instead of processing it in-process', async () => { + markInsideTriggerRun() + const documents = Array.from({ length: 1001 }, (_, index) => ({ + ...DOCUMENT, + documentId: `document-${index}`, + })) + dbChainMockFns.returning.mockResolvedValueOnce(documents.map((doc) => ({ id: doc.documentId }))) + mockBatchTrigger + .mockResolvedValueOnce({ batchId: 'batch-1' }) + .mockRejectedValueOnce(new Error('queue is full')) + + const result = await processDocumentsWithQueue( + documents, + 'knowledge-base-1', + {}, + 'request-1', + BILLING_ATTRIBUTION, + 'backfill' + ) + + expect(mockBatchTrigger).toHaveBeenCalledTimes(2) + expect(result).toMatchObject({ + requested: 1001, + accepted: 1000, + failed: 1, + failedDocumentIds: ['document-1000'], + }) + }) + it('returns acceptance separately from eventual child completion', async () => { markInsideTriggerRun() @@ -339,7 +458,8 @@ describe('processDocumentsWithQueue dispatch backend', () => { 'knowledge-base-1', {}, 'request-1', - BILLING_ATTRIBUTION + BILLING_ATTRIBUTION, + 'interactive' ) expect(result).toEqual({ requested: 1, accepted: 1, failed: 0, failedDocumentIds: [] }) @@ -361,7 +481,8 @@ describe('processDocumentsWithQueue dispatch backend', () => { 'knowledge-base-1', {}, 'request-1', - BILLING_ATTRIBUTION + BILLING_ATTRIBUTION, + 'interactive' ) expect(result).toEqual({ requested: 1, accepted: 1, failed: 0, failedDocumentIds: [] }) @@ -381,7 +502,8 @@ describe('processDocumentsWithQueue dispatch backend', () => { 'knowledge-base-1', {}, 'request-1', - BILLING_ATTRIBUTION + BILLING_ATTRIBUTION, + 'interactive' ) expect(result).toEqual({ @@ -504,7 +626,8 @@ describe('processDocumentsWithQueue dispatch backend', () => { 'knowledge-base-1', {}, 'request-1', - BILLING_ATTRIBUTION + BILLING_ATTRIBUTION, + 'interactive' ) expect(result).toEqual({ requested: 1, accepted: 1, failed: 0, failedDocumentIds: [] }) @@ -580,7 +703,8 @@ describe('processDocumentsWithQueue dispatch backend', () => { 'knowledge-base-1', {}, 'request-1', - BILLING_ATTRIBUTION + BILLING_ATTRIBUTION, + 'interactive' ) expect(result).toEqual({ requested: 1, accepted: 1, failed: 0, failedDocumentIds: [] }) @@ -625,7 +749,8 @@ describe('processDocumentsWithQueue dispatch backend', () => { 'knowledge-base-1', {}, 'request-1', - BILLING_ATTRIBUTION + BILLING_ATTRIBUTION, + 'interactive' ) expect(result).toEqual({ requested: 1, accepted: 1, failed: 0, failedDocumentIds: [] }) @@ -692,7 +817,8 @@ describe('processDocumentsWithQueue dispatch backend', () => { 'knowledge-base-1', {}, 'request-1', - BILLING_ATTRIBUTION + BILLING_ATTRIBUTION, + 'interactive' ) ).rejects.toThrow('document processing dispatches failed') @@ -728,7 +854,8 @@ describe('processDocumentsWithQueue dispatch backend', () => { 'knowledge-base-1', {}, 'request-1', - BILLING_ATTRIBUTION + BILLING_ATTRIBUTION, + 'interactive' ) expect(result).toEqual({ @@ -749,7 +876,8 @@ describe('processDocumentsWithQueue dispatch backend', () => { 'knowledge-base-1', {}, 'request-1', - BILLING_ATTRIBUTION + BILLING_ATTRIBUTION, + 'interactive' ) expect(result).toEqual({ requested: 2, accepted: 2, failed: 0, failedDocumentIds: [] }) @@ -761,7 +889,14 @@ describe('processDocumentsWithQueue dispatch backend', () => { it('returns an empty dispatch summary without resolving billing context', async () => { await expect( - processDocumentsWithQueue([], 'missing-knowledge-base', {}, 'request-1', undefined) + processDocumentsWithQueue( + [], + 'missing-knowledge-base', + {}, + 'request-1', + undefined, + 'interactive' + ) ).resolves.toEqual({ requested: 0, accepted: 0, failed: 0, failedDocumentIds: [] }) expect(mockBatchTrigger).not.toHaveBeenCalled() @@ -777,7 +912,8 @@ describe('processDocumentsWithQueue dispatch backend', () => { 'knowledge-base-1', {}, 'request-1', - BILLING_ATTRIBUTION + BILLING_ATTRIBUTION, + 'interactive' ) expect(mockBatchTrigger).toHaveBeenCalledTimes(1) @@ -792,7 +928,8 @@ describe('processDocumentsWithQueue dispatch backend', () => { 'knowledge-base-1', {}, 'request-1', - BILLING_ATTRIBUTION + BILLING_ATTRIBUTION, + 'interactive' ) ).resolves.toEqual({ requested: 1, accepted: 1, failed: 0, failedDocumentIds: [] }) @@ -834,7 +971,8 @@ describe('processDocumentsWithQueue dispatch backend', () => { 'knowledge-base-1', {}, 'request-1', - BILLING_ATTRIBUTION + BILLING_ATTRIBUTION, + 'interactive' ) ).rejects.toThrow('document processing dispatches failed') @@ -876,7 +1014,8 @@ describe('processDocumentsWithQueue dispatch backend', () => { 'knowledge-base-1', {}, 'request-1', - BILLING_ATTRIBUTION + BILLING_ATTRIBUTION, + 'interactive' ) ).resolves.toEqual({ requested: 1, accepted: 1, failed: 0, failedDocumentIds: [] }) @@ -958,7 +1097,8 @@ describe('processDocumentsWithQueue dispatch backend', () => { 'knowledge-base-1', {}, 'request-1', - BILLING_ATTRIBUTION + BILLING_ATTRIBUTION, + 'interactive' ) ).resolves.toEqual({ requested: 1, accepted: 1, failed: 0, failedDocumentIds: [] }) @@ -999,7 +1139,8 @@ describe('processDocumentsWithQueue attempt refund', () => { 'knowledge-base-1', {}, 'request-1', - BILLING_ATTRIBUTION + BILLING_ATTRIBUTION, + 'interactive' ) ).rejects.toThrow('trigger.dev region unavailable') @@ -1048,7 +1189,8 @@ describe('processDocumentsWithQueue attempt refund', () => { 'knowledge-base-1', {}, 'request-1', - BILLING_ATTRIBUTION + BILLING_ATTRIBUTION, + 'interactive' ) expect( @@ -1092,6 +1234,7 @@ describe('processDocumentsWithQueue under a connector sync lease', () => { {}, 'request-1', BILLING_ATTRIBUTION, + 'backfill', lease ) ).rejects.toBeInstanceOf(SyncLockLostException) @@ -1115,6 +1258,7 @@ describe('processDocumentsWithQueue under a connector sync lease', () => { {}, 'request-1', BILLING_ATTRIBUTION, + 'backfill', lease ) ).resolves.toEqual({ requested: 1, accepted: 1, failed: 0, failedDocumentIds: [] }) diff --git a/apps/sim/lib/knowledge/documents/processing-recovery.ts b/apps/sim/lib/knowledge/documents/processing-recovery.ts index 8d558070ff1..c9aeb3206ed 100644 --- a/apps/sim/lib/knowledge/documents/processing-recovery.ts +++ b/apps/sim/lib/knowledge/documents/processing-recovery.ts @@ -170,6 +170,8 @@ async function recoverStoredDocuments( mimeType: doc.mimeType, }, processingOptions: {}, + /** This sweep only selects connector-owned documents. */ + processingLane: 'backfill', requestId: token, processingQueueToken: token, processingQueuedAt: queuedAt.toISOString(), diff --git a/apps/sim/lib/knowledge/documents/service.ts b/apps/sim/lib/knowledge/documents/service.ts index 1aac135fb6e..16531d6fa01 100644 --- a/apps/sim/lib/knowledge/documents/service.ts +++ b/apps/sim/lib/knowledge/documents/service.ts @@ -103,6 +103,7 @@ import { recordUndispatchedDocumentFailure, } from '@/lib/knowledge/documents/processing-claim' import type { DocumentProcessingContinuation } from '@/lib/knowledge/documents/processing-continuation-dispatch' +import { documentProcessingQueueOptions } from '@/lib/knowledge/documents/processing-lane' import { enqueueKnowledgeDocumentProcessing } from '@/lib/knowledge/documents/processing-outbox-event' import { assertDocumentProcessingBillingContext, @@ -110,6 +111,7 @@ import { createOrganizationDocumentProcessingBillingContext, createWorkspaceDocumentProcessingBillingContext, type DocumentProcessingBillingContext, + type DocumentProcessingLane, type DocumentProcessingPayload, hasDocumentProcessingBillingScope, } from '@/lib/knowledge/documents/processing-payload' @@ -720,12 +722,14 @@ function buildJobPayload( processingQueueToken: string, processingQueuedAt: Date, chargedAtDispatch: boolean, - billingContext: DocumentProcessingBillingContext + billingContext: DocumentProcessingBillingContext, + lane: DocumentProcessingLane ): DocumentProcessingPayload { return createDocumentProcessingPayload( { knowledgeBaseId, documentId: doc.documentId, + processingLane: lane, docData: { filename: doc.filename, fileUrl: doc.fileUrl, @@ -1070,6 +1074,11 @@ async function bestEffortWithdrawDocumentsQueued( * pass. A successful Trigger.dev hand-off is only an accepted child run, not a * claim about its eventual processing outcome. A connector sync passes its * lease, and the queue write then lands only while the run still holds it. + * + * `lane` decides which per-tenant queue the work is admitted through, and is + * required rather than inferred so a new caller has to state whether someone is + * waiting on the result. It is stamped onto every payload so continuations of + * this pass resume in the same lane. */ export async function processDocumentsWithQueue( createdDocuments: DocumentData[], @@ -1077,6 +1086,7 @@ export async function processDocumentsWithQueue( processingOptions: ProcessingOptions, requestId: string, billingAttribution: BillingAttributionSnapshot | undefined, + lane: DocumentProcessingLane, lease?: ProcessingDispatchLease, executionContext?: DocumentProcessingExecutionContext ): Promise { @@ -1150,7 +1160,8 @@ export async function processDocumentsWithQueue( requestId, generation.processingQueuedAt, generation.chargedAtDispatch, - billingContext + billingContext, + lane ) }) @@ -1239,6 +1250,7 @@ async function dispatchViaBatchTrigger( `knowledgeBaseId:${payload.knowledgeBaseId}`, `documentId:${payload.documentId}`, ], + ...documentProcessingQueueOptions(payload), region, }, })) @@ -1260,12 +1272,27 @@ async function dispatchViaBatchTrigger( * Only a total dispatch failure raises, so a chunk failing alone would leave its * documents at `pending` with nothing recording why. Processing them here is * slower than the queue but does not drop the work. + * + * Backfill is excluded deliberately. Such a chunk is rejected precisely when + * the tenant is already saturated, and the worker absorbing it is the one + * holding the connector lease: the in-flight count stays bounded, but the + * total work does not, so a rejected chunk can outlast the lease it is + * running under. Leaving those documents undispatched reports them failed, + * which is what the next sync's stuck-document sweep reclaims. Interactive + * work has no such sweep behind it, so it still falls back. */ - if (undispatched.length > 0) { + const fallback = undispatched.filter((payload) => payload.processingLane !== 'backfill') + const swept = undispatched.length - fallback.length + if (swept > 0) { + logger.warn( + `[${requestId}] Leaving ${swept} backfill documents for the stuck-document sweep after failed enqueue` + ) + } + if (fallback.length > 0) { logger.warn( - `[${requestId}] Processing ${undispatched.length} documents in-process after failed enqueue` + `[${requestId}] Processing ${fallback.length} documents in-process after failed enqueue` ) - const directlyDispatchedIds = await dispatchInProcess(undispatched, requestId, executionContext) + const directlyDispatchedIds = await dispatchInProcess(fallback, requestId, executionContext) for (const documentId of directlyDispatchedIds) dispatchedIds.add(documentId) } @@ -3102,6 +3129,12 @@ export async function createSingleDocument( documentId, processingOptions: options.processing.processingOptions, billingAttribution: options.processing.billingAttribution, + /** + * Every caller of this function is a person adding a document — an + * upload session, a workspace-file attach, a direct create. Connector + * ingestion builds its documents through the sync engine instead. + */ + processingLane: 'interactive', }) } @@ -3544,7 +3577,9 @@ export async function retryDocumentProcessing( knowledgeBaseId, {}, requestId, - billingAttribution + billingAttribution, + /** One document, retried by hand: a person is waiting on this one. */ + 'interactive' ) if (dispatch.failed > 0 || dispatch.accepted !== 1) { throw new Error(`Document processing dispatch was not accepted for ${documentId}`) diff --git a/apps/sim/lib/knowledge/documents/storage-billing.test.ts b/apps/sim/lib/knowledge/documents/storage-billing.test.ts index 07677ae676e..3a327f7d25d 100644 --- a/apps/sim/lib/knowledge/documents/storage-billing.test.ts +++ b/apps/sim/lib/knowledge/documents/storage-billing.test.ts @@ -236,6 +236,7 @@ describe('knowledge document storage attribution', () => { expect(mockEnqueueKnowledgeDocumentProcessing).toHaveBeenCalledWith(dbChainMock.db, { knowledgeBaseId: 'knowledge-base-1', documentId: 'document-1', + processingLane: 'interactive', processingOptions: { lang: 'en' }, billingAttribution, }) diff --git a/apps/sim/lib/knowledge/documents/types.ts b/apps/sim/lib/knowledge/documents/types.ts index 3b0bd225f99..4bf7435f690 100644 --- a/apps/sim/lib/knowledge/documents/types.ts +++ b/apps/sim/lib/knowledge/documents/types.ts @@ -27,11 +27,11 @@ export const MAX_PROCESSING_ATTEMPTS = 5 * `STALE_PROCESSING_MINUTES` bounds a run that has already begun, derived from * the task's own duration and retry budget. Queue *wait* is a different * quantity: it is backlog / concurrency, not run duration. - * `document-processing-queue` has a global concurrency shared by every - * workspace, so a corpus large enough to approach - * `CONNECTOR_SYNC_MAX_DURATION_SECONDS` enqueues thousands of documents that - * drain in waves of that width — at roughly a minute of occupancy each, a few - * hours, and longer while other workspaces hold slots. + * Backfill drains through a per-tenant copy of the backfill queue, so a corpus + * large enough to approach `CONNECTOR_SYNC_MAX_DURATION_SECONDS` enqueues + * thousands of documents that drain in waves of that concurrency — at roughly a + * minute of occupancy each, a few hours. Another tenant's corpus no longer + * extends that wait, but the shared environment concurrency limit still can. * * Four hours is chosen against three bounds that are all constants in this * repository rather than any one deployment's corpus: it is well above that From de19e11423784770cba50b38e0d944910800b786 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Thu, 17 Sep 2026 12:41:23 -0700 Subject: [PATCH 08/15] improvement(provenance): enforce tracked durable reads (#7938) * improvement(provenance): enforce tracked durable reads * fix(provenance): mark legacy attachment replay fixtures --- .../self-hosting/environment-variables.mdx | 3 +- .../knowledge/search/route.provenance.test.ts | 76 ++---- .../handlers/agent/agent-handler.test.ts | 11 +- .../executor/handlers/agent/memory.test.ts | 50 +--- apps/sim/executor/handlers/agent/memory.ts | 58 +---- apps/sim/lib/copilot/tools/handlers/vfs.ts | 1 - apps/sim/lib/core/config/env.ts | 1 - ...able-secret-provenance-enforcement.test.ts | 202 --------------- .../durable-secret-provenance-enforcement.ts | 237 ------------------ ...urable-secret-provenance-telemetry.test.ts | 58 +++++ .../durable-secret-provenance-telemetry.ts | 67 +++++ .../durable-secret-provenance.test.ts | 46 +--- .../execution/durable-secret-provenance.ts | 31 +-- .../lib/function-execution/execute-request.ts | 1 - .../file/operations.provenance.test.ts | 78 +++--- apps/sim/lib/internal/file/operations.ts | 2 - apps/sim/lib/internal/memory/provenance.ts | 4 +- .../external-file-provenance.integration.ts | 3 - .../upload-read-provenance.integration.ts | 2 - .../lib/knowledge/api/secret-provenance.ts | 2 - .../application/add-workspace-files.ts | 2 +- apps/sim/lib/knowledge/application/chunks.ts | 2 +- .../application/read-indexed-document.ts | 9 +- .../application/read-search-document.ts | 10 +- apps/sim/lib/knowledge/application/search.ts | 34 +-- .../lib/knowledge/secret-provenance.test.ts | 58 ++--- apps/sim/lib/knowledge/secret-provenance.ts | 74 +----- .../lib/memory/application/use-cases.test.ts | 14 -- apps/sim/lib/memory/application/use-cases.ts | 20 +- .../message-provenance.postgres.test.ts | 15 +- .../sim/lib/memory/message-provenance.test.ts | 14 +- apps/sim/lib/memory/secret-provenance.ts | 2 +- .../rows/secret-provenance.postgres.test.ts | 78 ++---- .../lib/table/rows/secret-provenance.test.ts | 70 ++---- apps/sim/lib/table/rows/secret-provenance.ts | 37 +-- .../workspace-file-secret-provenance.test.ts | 131 ++-------- .../workspace-file-secret-provenance.ts | 112 ++------- 37 files changed, 355 insertions(+), 1260 deletions(-) delete mode 100644 apps/sim/lib/execution/durable-secret-provenance-enforcement.test.ts delete mode 100644 apps/sim/lib/execution/durable-secret-provenance-enforcement.ts create mode 100644 apps/sim/lib/execution/durable-secret-provenance-telemetry.test.ts create mode 100644 apps/sim/lib/execution/durable-secret-provenance-telemetry.ts diff --git a/apps/docs/content/docs/platform/self-hosting/environment-variables.mdx b/apps/docs/content/docs/platform/self-hosting/environment-variables.mdx index f97b6f246df..d870ef0e354 100644 --- a/apps/docs/content/docs/platform/self-hosting/environment-variables.mdx +++ b/apps/docs/content/docs/platform/self-hosting/environment-variables.mdx @@ -234,9 +234,10 @@ width. Check what you pick there, or upgrade Ollama. | `COPILOT_API_KEY` | API key for Chat. Without it the Sim Chat block, scheduled prompt jobs, and Inbox cannot run | | `NEXT_PUBLIC_CHAT_DISABLED` | Set to `true` to hide the Chat module: the workspace lands on your first workflow, with no chats list, scheduled tasks, or editor Chat panel. Chat is shown when unset; `npx sim-setup` sets it for you if you skip the chat key | | `PII_URL` | Base URL of the Presidio service backing PII detection and redaction. The Helm chart wires it to its own `pii` Service when `pii.enabled`; on Compose point it at the PII service on your network. The default `http://localhost:5001` exists only in local development, and leaving it makes redaction fail | -| `DURABLE_SECRET_PROVENANCE_ENFORCED_SURFACES` | Durable stores where a value whose secret provenance was never recorded fails the run instead of logging a warning. `all`, or a comma-separated subset of `memory`, `table-row`, `knowledge`, `workspace-file`. Unset (nothing enforced) by default | | `ADMIN_API_KEY` | Admin API key for GitOps operations and organization provisioning | +Tracked memory, table rows, knowledge content, and workspace files require valid secret provenance before entering a model or a trusted runtime. Records with a null provenance tracking marker retain legacy compatibility. + ## Enterprise Features Enterprise features are unlocked by configuration rather than billing on self-hosted deployments. One switch turns on the full set; per-feature flags below it override the switch either way. diff --git a/apps/sim/app/api/v2/knowledge/search/route.provenance.test.ts b/apps/sim/app/api/v2/knowledge/search/route.provenance.test.ts index 7e847cb2797..f295359c749 100644 --- a/apps/sim/app/api/v2/knowledge/search/route.provenance.test.ts +++ b/apps/sim/app/api/v2/knowledge/search/route.provenance.test.ts @@ -107,10 +107,6 @@ import { } from '@sim/testing' import { NextRequest } from 'next/server' import { env } from '@/lib/core/config/env' -import { - isDurableSecretProvenanceEnforced, - resetDurableSecretProvenanceEnforcementCache, -} from '@/lib/execution/durable-secret-provenance-enforcement' import { createKnowledgeDocumentSourceValue } from '@/lib/knowledge/secret-provenance' import { POST } from '@/app/api/v2/knowledge/search/route' import { ResolvedSecretTraceRegistry } from '@/executor/utils/resolved-secret-trace-registry' @@ -211,12 +207,6 @@ function providerPayload() { return JSON.parse(provider.fetch.mock.calls[0][1].body) } -function enforceKnowledge(enforced: boolean) { - env.DURABLE_SECRET_PROVENANCE_ENFORCED_SURFACES = enforced ? 'all' : '' - resetDurableSecretProvenanceEnforcementCache() - expect(isDurableSecretProvenanceEnforced('knowledge')).toBe(enforced) -} - async function requestSearch(overrides: Partial = {}) { return POST( new NextRequest('http://localhost/api/v2/knowledge/search', { @@ -230,7 +220,6 @@ async function requestSearch(overrides: Partial = {}) { beforeEach(() => { vi.clearAllMocks() resetDbChainMock() - enforceKnowledge(true) env.COHERE_API_KEY = 'synthetic-cohere-key' provider.decrypt.mockResolvedValue({ decrypted: SECRET }) provider.fetch.mockResolvedValue( @@ -264,26 +253,22 @@ beforeEach(() => { /** The route, use case, sidecar binding/import, registry, projection and provider request builder are real. */ describe('Knowledge search provenance through the V2 route and reranker HTTP boundary', () => { - it.each([false, true])( - 'redacts current known-secret chunks with enforcement=%s', - async (enforced) => { - enforceKnowledge(enforced) - seedSidecar('exact') - const response = await requestSearch() - const body = await response.json() - expect(response.status).toBe(200) - expect(body.data.rerankerStatus).toBe('applied') - expect(providerPayload().documents).toEqual([CONTENT.replace(SECRET, '{{TOKEN}}')]) - expect(body.data.results[0].content).toBe(CONTENT) - expect(provider.decrypt).toHaveBeenCalledWith('synthetic-encrypted-token') - expect(mocks.generateEmbedding).toHaveBeenCalledWith( - requestInput.query, - expect.anything(), - 'workspace-1', - undefined - ) - } - ) + it('redacts current known-secret chunks', async () => { + seedSidecar('exact') + const response = await requestSearch() + const body = await response.json() + expect(response.status).toBe(200) + expect(body.data.rerankerStatus).toBe('applied') + expect(providerPayload().documents).toEqual([CONTENT.replace(SECRET, '{{TOKEN}}')]) + expect(body.data.results[0].content).toBe(CONTENT) + expect(provider.decrypt).toHaveBeenCalledWith('synthetic-encrypted-token') + expect(mocks.generateEmbedding).toHaveBeenCalledWith( + requestInput.query, + expect.anything(), + 'workspace-1', + undefined + ) + }) it('does not assign a billing owner secret name to a workspace-key caller', async () => { seedSidecar('exact') @@ -314,7 +299,7 @@ describe('Knowledge search provenance through the V2 route and reranker HTTP bou }) it.each(['unknown', 'missing', 'stale', 'malformed'] as const)( - 'refuses %s tracked provenance before provider HTTP when enforcement is enabled', + 'refuses %s tracked provenance before provider HTTP', async (status) => { seedSidecar(status) const response = await requestSearch() @@ -326,27 +311,12 @@ describe('Knowledge search provenance through the V2 route and reranker HTTP bou } ) - it.each(['unknown', 'missing', 'stale', 'malformed'] as const)( - 'preserves existing flag-off compatibility for %s sidecars', - async (status) => { - enforceKnowledge(false) - seedSidecar(status) - const response = await requestSearch() - expect(response.status).toBe(200) - expect(providerPayload().documents).toEqual([CONTENT]) - } - ) - - it.each([false, true])( - 'keeps pre-tracking NULL rows readable with enforcement=%s', - async (enforced) => { - enforceKnowledge(enforced) - seedSidecar('legacy') - const response = await requestSearch() - expect(response.status).toBe(200) - expect(providerPayload().documents).toEqual([CONTENT]) - } - ) + it('keeps pre-tracking NULL rows readable', async () => { + seedSidecar('legacy') + const response = await requestSearch() + expect(response.status).toBe(200) + expect(providerPayload().documents).toEqual([CONTENT]) + }) it('does not subject a raw public read without reranking to durable-model enforcement', async () => { seedSidecar('unknown') diff --git a/apps/sim/executor/handlers/agent/agent-handler.test.ts b/apps/sim/executor/handlers/agent/agent-handler.test.ts index b5b61ec6c1d..85d14bd6e27 100644 --- a/apps/sim/executor/handlers/agent/agent-handler.test.ts +++ b/apps/sim/executor/handlers/agent/agent-handler.test.ts @@ -394,7 +394,10 @@ describe('AgentBlockHandler', () => { expect(inputs).toEqual(original) queueTableRows(schemaMock.memory, [ - { data: [...stored, { role: 'assistant', content: 'First answer' }] }, + { + secretProvenanceVersion: null, + data: [...stored, { role: 'assistant', content: 'First answer' }], + }, ]) mockGetProviderFromModel.mockReturnValue('anthropic') const nextContext = { ...mockContext, executionId: 'exec-2' } @@ -428,6 +431,7 @@ describe('AgentBlockHandler', () => { mockGetProviderFromModel.mockReturnValue('openai') queueTableRows(schemaMock.memory, [ { + secretProvenanceVersion: null, data: [ { role: 'user', content: 'Analyze this file', executionId: 'exec-1', files: [file] }, ], @@ -448,7 +452,9 @@ describe('AgentBlockHandler', () => { it('saves a new attachment appended to an existing conversation', async () => { mockGetProviderFromModel.mockReturnValue('openai') - queueTableRows(schemaMock.memory, [{ data: [{ role: 'assistant', content: 'Hello' }] }]) + queueTableRows(schemaMock.memory, [ + { secretProvenanceVersion: null, data: [{ role: 'assistant', content: 'Hello' }] }, + ]) await handler.execute({ ...mockContext, executionId: 'exec-2' }, mockBlock, { model: 'gpt-4o', memoryType: 'conversation', @@ -468,6 +474,7 @@ describe('AgentBlockHandler', () => { const hydrate = vi.spyOn(userFileBase64, 'hydrateUserFilesWithBase64') queueTableRows(schemaMock.memory, [ { + secretProvenanceVersion: null, data: [ { role: 'user', content: 'Old file', files: [file] }, { role: 'assistant', content: 'Recent answer' }, diff --git a/apps/sim/executor/handlers/agent/memory.test.ts b/apps/sim/executor/handlers/agent/memory.test.ts index 96f629e62ba..497958954db 100644 --- a/apps/sim/executor/handlers/agent/memory.test.ts +++ b/apps/sim/executor/handlers/agent/memory.test.ts @@ -1,18 +1,9 @@ import { loggerMock, queueTableRows, resetDbChainMock, schemaMock } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' -const { mockDecryptSecret, mockRedactObjectStrings, mockIsEnforced, mockReportUnrecorded } = - vi.hoisted(() => ({ - mockDecryptSecret: vi.fn(), - mockRedactObjectStrings: vi.fn(async (value: unknown) => value), - mockIsEnforced: vi.fn(() => false), - mockReportUnrecorded: vi.fn(), - })) - -vi.mock('@/lib/execution/durable-secret-provenance-enforcement', () => ({ - DURABLE_SECRET_PROVENANCE_SURFACES: ['memory', 'table-row', 'knowledge'], - isDurableSecretProvenanceEnforced: mockIsEnforced, - reportUnrecordedDurableProvenance: mockReportUnrecorded, +const { mockDecryptSecret, mockRedactObjectStrings } = vi.hoisted(() => ({ + mockDecryptSecret: vi.fn(), + mockRedactObjectStrings: vi.fn(async (value: unknown) => value), })) vi.mock('@/lib/core/security/encryption', () => ({ @@ -56,7 +47,6 @@ describe('Memory', () => { beforeEach(() => { vi.clearAllMocks() resetDbChainMock() - mockIsEnforced.mockReturnValue(false) mockDecryptSecret.mockImplementation(async (encryptedValue: string) => ({ decrypted: `decrypted:${encryptedValue}`, })) @@ -313,6 +303,7 @@ describe('Memory', () => { async ({ provider, render }) => { queueTableRows(schemaMock.memory, [ { + secretProvenanceVersion: null, data: [ { role: 'user', @@ -359,7 +350,7 @@ describe('Memory', () => { context: 'execution', } queueTableRows(schemaMock.memory, [ - { data: [{ role: 'user', content: 'File', files: [file] }] }, + { secretProvenanceVersion: null, data: [{ role: 'user', content: 'File', files: [file] }] }, ]) const context = { workspaceId: 'workspace-1', @@ -388,6 +379,7 @@ describe('Memory', () => { it('bounds historical file loading even when the messages contain no text', async () => { queueTableRows(schemaMock.memory, [ { + secretProvenanceVersion: null, data: Array.from({ length: MEMORY.MAX_REPLAY_FILE_REFERENCES + 1 }, () => ({ role: 'user', content: '', @@ -406,6 +398,7 @@ describe('Memory', () => { it('does not carry inline-only or malformed file objects into a later turn', async () => { queueTableRows(schemaMock.memory, [ { + secretProvenanceVersion: null, data: [ { role: 'user', @@ -695,33 +688,7 @@ describe('Memory', () => { expect(mockDecryptSecret).not.toHaveBeenCalled() }) - /** Trace 2's shape: a stored memory a previous run could not vouch for. */ - it('reads a memory with unrecorded provenance while the surface stays open', async () => { - const registry = new ResolvedSecretTraceRegistry([], { - userId: 'user-1', - workspaceId: 'workspace-1', - }) - vi.spyOn(memoryService as any, 'fetchMemory').mockResolvedValueOnce({ - messages: [{ role: 'user', content: 'how do i see my tickets?' }], - provenance: { status: 'unknown' }, - }) - - const messages = await memoryService.fetchMemoryMessages( - createContext(registry) as never, - inputs - ) - - expect(messages).toEqual([{ role: 'user', content: 'how do i see my tickets?' }]) - expect(registry.isPermanentlyIncomplete()).toBe(false) - expect(mockReportUnrecorded).toHaveBeenCalledWith({ - surface: 'memory', - cause: 'stored-memory-provenance-unknown', - workspaceId: 'workspace-1', - }) - }) - - it('refuses that same memory once the memory surface is closed', async () => { - mockIsEnforced.mockReturnValue(true) + it('refuses tracked memory with unknown provenance', async () => { const registry = new ResolvedSecretTraceRegistry([], { userId: 'user-1', workspaceId: 'workspace-1', @@ -734,7 +701,6 @@ describe('Memory', () => { await expect( memoryService.fetchMemoryMessages(createContext(registry) as never, inputs) ).rejects.toThrow() - expect(mockReportUnrecorded).not.toHaveBeenCalled() }) }) diff --git a/apps/sim/executor/handlers/agent/memory.ts b/apps/sim/executor/handlers/agent/memory.ts index 2da2bbec8d9..a55c02bc725 100644 --- a/apps/sim/executor/handlers/agent/memory.ts +++ b/apps/sim/executor/handlers/agent/memory.ts @@ -11,10 +11,6 @@ import { importDurableSecretProvenance, mergeDurableSecretProvenance, } from '@/lib/execution/durable-secret-provenance' -import { - isDurableSecretProvenanceEnforced, - reportUnrecordedDurableProvenance, -} from '@/lib/execution/durable-secret-provenance-enforcement' import { mergeFileKeys } from '@/lib/execution/payloads/access-keys' import { redactObjectStrings } from '@/lib/logs/execution/pii-redaction' import { lockMemoryConversationInTx } from '@/lib/memory/locks' @@ -101,12 +97,8 @@ export class Memory { const staged = new ResolvedSecretTraceRegistry([], scope, { staged: true }) staged.mergeToolCallRegistry(ctx.resolvedSecretTraceRegistry) includeRecovered = - (await importDurableSecretProvenance( - staged, - selection.select(messages, true), - messages, - 'memory' - )) && staged.getModelEgressSnapshot().complete + (await importDurableSecretProvenance(staged, selection.select(messages, true), messages)) && + staged.getModelEgressSnapshot().complete if (includeRecovered) { for (const message of messages) { const stagedMessage = new ResolvedSecretTraceRegistry([], scope, { staged: true }) @@ -114,8 +106,7 @@ export class Memory { !(await importDurableSecretProvenance( stagedMessage, selection.select([message], true), - message, - 'memory' + message )) || !stagedMessage.getModelEgressSnapshot().complete ) { @@ -146,31 +137,15 @@ export class Memory { const selectProvenance = (values: readonly unknown[]) => selection.select(values, includeRecovered) const selectedProvenance = selectProvenance(messages) - /** - * Unrecorded provenance is checked through the same policy the shared import uses, so stored - * memory written by a run that could not vouch does not permanently refuse every later turn. - */ - let refuseStoredProvenance: boolean - if (selectedProvenance.status === 'unknown') { - refuseStoredProvenance = isDurableSecretProvenanceEnforced('memory') - if (!refuseStoredProvenance) { - reportUnrecordedDurableProvenance({ - surface: 'memory', - cause: 'stored-memory-provenance-unknown', - ...(ctx.workspaceId ? { workspaceId: ctx.workspaceId } : {}), - }) - } - } else { - refuseStoredProvenance = - (selectedProvenance.entries.length > 0 && !ctx.resolvedSecretTraceRegistry) || - (ctx.resolvedSecretTraceRegistry !== undefined && - !(await importDurableSecretProvenance( - ctx.resolvedSecretTraceRegistry, - selectedProvenance, - messages, - 'memory' - ))) - } + const refuseStoredProvenance = + selectedProvenance.status === 'unknown' || + (selectedProvenance.entries.length > 0 && !ctx.resolvedSecretTraceRegistry) || + (ctx.resolvedSecretTraceRegistry !== undefined && + !(await importDurableSecretProvenance( + ctx.resolvedSecretTraceRegistry, + selectedProvenance, + messages + ))) if (refuseStoredProvenance) { refuseResolvedSecretProjection({ site: 'memory.storedProvenanceImport', @@ -187,14 +162,7 @@ export class Memory { [], ctx.resolvedSecretTraceRegistry?.exportProvenance().scope ) - if ( - !(await importDurableSecretProvenance( - modelRegistry, - messageProvenance, - message, - 'memory' - )) - ) { + if (!(await importDurableSecretProvenance(modelRegistry, messageProvenance, message))) { refuseResolvedSecretProjection({ site: 'memory.messageProvenanceImport', message: MEMORY_CONTENT_REFUSAL, diff --git a/apps/sim/lib/copilot/tools/handlers/vfs.ts b/apps/sim/lib/copilot/tools/handlers/vfs.ts index 91cdab45981..2f71288ebba 100644 --- a/apps/sim/lib/copilot/tools/handlers/vfs.ts +++ b/apps/sim/lib/copilot/tools/handlers/vfs.ts @@ -130,7 +130,6 @@ async function canReturnWorkspaceFileValue( registry: context.resolvedSecretTraceRegistry, view: provenanceView, value, - actorUserId: context.userId, })) ) { return false diff --git a/apps/sim/lib/core/config/env.ts b/apps/sim/lib/core/config/env.ts index 780a569bcf6..8d61dba5005 100644 --- a/apps/sim/lib/core/config/env.ts +++ b/apps/sim/lib/core/config/env.ts @@ -189,7 +189,6 @@ export const env = createEnv({ BILLING_CONCURRENCY_LIMIT_ENTERPRISE: z.string().optional(), // In-flight executions per Enterprise billing account (metadata-overridable) BILLING_ENABLED: z.boolean().optional(), // Enable billing enforcement and usage tracking TRIGGER_EU_REGION: z.boolean().optional(), // Route Trigger.dev runs to eu-central-1 instead of the default us-east-1 (fallback for the trigger-eu-region flag when AppConfig is not the source of truth) - DURABLE_SECRET_PROVENANCE_ENFORCED_SURFACES: z.string().optional(), // Durable surfaces where unrecorded secret provenance fails the run instead of logging a warning: "all", or a comma-separated subset of memory,table-row,knowledge,workspace-file (default: none enforced) // Table feature limits (per plan). Apply when billing is disabled (free tier defaults) or for billed plans. FREE_TABLES_LIMIT: z.number().optional(), // Max user tables per workspace on free tier (default: 5) diff --git a/apps/sim/lib/execution/durable-secret-provenance-enforcement.test.ts b/apps/sim/lib/execution/durable-secret-provenance-enforcement.test.ts deleted file mode 100644 index e4b1c092356..00000000000 --- a/apps/sim/lib/execution/durable-secret-provenance-enforcement.test.ts +++ /dev/null @@ -1,202 +0,0 @@ -/** - * @vitest-environment node - */ -import { beforeEach, describe, expect, it, vi } from 'vitest' - -const { mockEnv, mockLogger, mockPersistenceLogger, mockRecordAudit } = vi.hoisted(() => ({ - mockEnv: {} as Record, - mockLogger: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() }, - mockPersistenceLogger: { error: vi.fn() }, - mockRecordAudit: vi.fn(), -})) - -vi.mock('@/lib/core/config/env', () => ({ env: mockEnv })) -vi.mock('@sim/logger', () => ({ - createLogger: (module: string) => - module === 'DurableSecretProvenancePersistence' ? mockPersistenceLogger : mockLogger, -})) -/** Literal values rather than the real constants: these reach the database and the trail. */ -vi.mock('@sim/audit', () => ({ - recordAudit: mockRecordAudit, - AuditAction: { SECRET_PROVENANCE_UNRECORDED: 'secret_provenance.unrecorded' }, - AuditResourceType: { SECRET_PROVENANCE: 'secret_provenance' }, -})) - -import { - DURABLE_SECRET_PROVENANCE_SURFACES, - isDurableSecretProvenanceEnforced, - reportDurableSecretProvenanceRefusal, - reportDurableSecretProvenanceWrite, - reportUnrecordedDurableProvenance, - resetDurableSecretProvenanceEnforcementCache, -} from '@/lib/execution/durable-secret-provenance-enforcement' - -function configure(value: string | undefined): void { - mockEnv.DURABLE_SECRET_PROVENANCE_ENFORCED_SURFACES = value - resetDurableSecretProvenanceEnforcementCache() -} - -describe('durable secret provenance enforcement', () => { - beforeEach(() => { - vi.clearAllMocks() - configure(undefined) - }) - - it('enforces nothing by default, so unrecorded provenance warns instead of latching', () => { - for (const surface of DURABLE_SECRET_PROVENANCE_SURFACES) { - expect(isDurableSecretProvenanceEnforced(surface)).toBe(false) - } - }) - - it('closes one surface at a time without touching the others', () => { - configure('table-row') - - expect(isDurableSecretProvenanceEnforced('table-row')).toBe(true) - expect(isDurableSecretProvenanceEnforced('memory')).toBe(false) - expect(isDurableSecretProvenanceEnforced('knowledge')).toBe(false) - }) - - it('accepts a comma-separated subset, ignoring case and padding', () => { - configure(' Memory , TABLE-ROW ') - - expect(isDurableSecretProvenanceEnforced('memory')).toBe(true) - expect(isDurableSecretProvenanceEnforced('table-row')).toBe(true) - expect(isDurableSecretProvenanceEnforced('knowledge')).toBe(false) - }) - - it('closes every surface on "all"', () => { - configure('all') - - for (const surface of DURABLE_SECRET_PROVENANCE_SURFACES) { - expect(isDurableSecretProvenanceEnforced(surface)).toBe(true) - } - }) - - it('reports an unrecognized surface rather than silently enforcing nothing', () => { - configure('memory,not-a-surface') - - expect(isDurableSecretProvenanceEnforced('memory')).toBe(true) - expect(mockLogger.error).toHaveBeenCalledWith( - 'Ignoring unrecognized durable secret provenance surfaces', - expect.objectContaining({ unrecognized: ['not-a-surface'] }) - ) - }) - - /** - * `workspace-file` was this test's example of an unrecognized name while the env documentation - * already offered it — so anyone who set it got a silent no-op and the fail-closed posture they - * were trying to change. It is a real surface now. - */ - it('recognizes every surface its own configuration documents', () => { - configure('workspace-file') - - expect(isDurableSecretProvenanceEnforced('workspace-file')).toBe(true) - expect(isDurableSecretProvenanceEnforced('table-row')).toBe(false) - expect(mockLogger.error).not.toHaveBeenCalled() - }) - - it('reports at error with the surface, cause, and affected count so it survives every LOG_LEVEL default', () => { - reportUnrecordedDurableProvenance({ - surface: 'table-row', - cause: 'row-sidecar-not-exact', - affectedCount: 8, - workspaceId: 'workspace-1', - }) - - expect(mockLogger.warn).not.toHaveBeenCalled() - expect(mockLogger.error).toHaveBeenCalledWith( - 'Proceeding on unrecorded durable secret provenance', - { - surface: 'table-row', - cause: 'row-sidecar-not-exact', - enforced: false, - affectedCount: 8, - workspaceId: 'workspace-1', - } - ) - }) - it('records a workspace-visible audit entry so a fail-open read is not only in our logs', () => { - reportUnrecordedDurableProvenance({ - surface: 'table-row', - cause: 'row-sidecar-not-exact', - affectedCount: 8, - workspaceId: 'workspace-1', - actorUserId: 'user-1', - }) - - expect(mockRecordAudit).toHaveBeenCalledWith( - expect.objectContaining({ - workspaceId: 'workspace-1', - actorId: 'user-1', - action: 'secret_provenance.unrecorded', - resourceType: 'secret_provenance', - resourceId: 'table-row', - metadata: { surface: 'table-row', cause: 'row-sidecar-not-exact', affectedCount: 8 }, - }) - ) - }) - - it('still records the entry when the surface cannot name an actor', () => { - reportUnrecordedDurableProvenance({ - surface: 'memory', - cause: 'durable-provenance-unknown', - workspaceId: 'workspace-1', - }) - - expect(mockRecordAudit).toHaveBeenCalledWith(expect.objectContaining({ actorId: null })) - }) - - /** An entry with no workspace names nobody it concerns; the log line still carries it. */ - it('skips the audit entry when there is no workspace to show it to', () => { - reportUnrecordedDurableProvenance({ surface: 'knowledge', cause: 'durable-provenance-unknown' }) - - expect(mockRecordAudit).not.toHaveBeenCalled() - expect(mockLogger.error).toHaveBeenCalled() - }) - - it('separates non-exact write telemetry from permissive reads and copies only safe fields', () => { - const report = { - surface: 'knowledge' as const, - status: 'unknown' as const, - cause: 'source-provenance-unknown' as const, - recordCount: 2, - workspaceId: 'workspace-1', - resourceId: 'document-1', - content: 'private document content', - entries: [{ secretName: 'PRIVATE_TOKEN', ciphertext: 'private encrypted value' }], - } - reportDurableSecretProvenanceWrite(report) - - expect(mockPersistenceLogger.error).toHaveBeenCalledWith( - 'Writing non-exact durable secret provenance', - { - surface: 'knowledge', - status: 'unknown', - cause: 'source-provenance-unknown', - recordCount: 2, - workspaceId: 'workspace-1', - resourceId: 'document-1', - } - ) - expect(mockLogger.error).not.toHaveBeenCalled() - expect(mockRecordAudit).not.toHaveBeenCalled() - expect(isDurableSecretProvenanceEnforced('knowledge')).toBe(false) - }) - - it('reports existing refusals without resolving or changing enforcement', () => { - configure('workspace-file') - reportDurableSecretProvenanceRefusal({ - surface: 'workspace-file', - cause: 'workspace-file-opaque-secret-content', - }) - - expect(mockPersistenceLogger.error).toHaveBeenCalledWith( - 'Refusing unavailable durable secret provenance', - { surface: 'workspace-file', cause: 'workspace-file-opaque-secret-content' } - ) - expect(mockLogger.error).not.toHaveBeenCalled() - expect(mockRecordAudit).not.toHaveBeenCalled() - expect(isDurableSecretProvenanceEnforced('workspace-file')).toBe(true) - expect(isDurableSecretProvenanceEnforced('memory')).toBe(false) - }) -}) diff --git a/apps/sim/lib/execution/durable-secret-provenance-enforcement.ts b/apps/sim/lib/execution/durable-secret-provenance-enforcement.ts deleted file mode 100644 index c8b9c382055..00000000000 --- a/apps/sim/lib/execution/durable-secret-provenance-enforcement.ts +++ /dev/null @@ -1,237 +0,0 @@ -import { AuditAction, AuditResourceType, recordAudit } from '@sim/audit' -import { createLogger } from '@sim/logger' -import { env } from '@/lib/core/config/env' - -const logger = createLogger('DurableSecretProvenanceEnforcement') -const persistenceLogger = createLogger('DurableSecretProvenancePersistence') - -/** - * Durable stores that can hand a run a value whose secret provenance was never recorded. - * - * Named by the call site rather than derived, so the surface survives refactors and stays - * greppable — the same convention the projection-refusal `site` strings use. - * - * One policy governs all of them: an **absence** — nobody recorded what these bytes carry — may be - * read, with an audit entry naming the surface, because a value nobody recorded says exactly what - * an untracked one does. A **taint** — a writer that knew secrets were present and could not map - * them to this output — is refused, and no policy relaxes it. - * - * Only `workspace-file` stores the difference, and that is deliberate rather than drift. On the - * other surfaces every non-exact sidecar comes from one condition, an incomplete incoming bundle - * or registry, which is always an absence; two stored statuses say everything there is to say. - * Files are the only surface that derives one stored object from another — an archive extracted - * into children, a generated asset, a transcoded output — so they are the only one that can refuse - * on purpose, and the only one with two claims to keep apart. Adding a third status elsewhere would - * encode a distinction that surface cannot make; removing it here would collapse one that matters. - */ -export const DURABLE_SECRET_PROVENANCE_SURFACES = [ - 'memory', - 'table-row', - 'knowledge', - 'workspace-file', -] as const - -export type DurableSecretProvenanceSurface = (typeof DURABLE_SECRET_PROVENANCE_SURFACES)[number] - -/** Reads the configured surfaces once; an unrecognized name is reported rather than assumed. */ -function resolveEnforcedSurfaces(): ReadonlySet { - const configured = env.DURABLE_SECRET_PROVENANCE_ENFORCED_SURFACES?.trim() - if (!configured) return new Set() - - const requested = configured - .split(',') - .map((entry) => entry.trim().toLowerCase()) - .filter((entry) => entry.length > 0) - if (requested.includes('all')) return new Set(DURABLE_SECRET_PROVENANCE_SURFACES) - - const enforced = new Set() - const unrecognized: string[] = [] - for (const entry of requested) { - const surface = DURABLE_SECRET_PROVENANCE_SURFACES.find((candidate) => candidate === entry) - if (surface) enforced.add(surface) - else unrecognized.push(entry) - } - if (unrecognized.length > 0) { - logger.error('Ignoring unrecognized durable secret provenance surfaces', { - unrecognized, - supported: [...DURABLE_SECRET_PROVENANCE_SURFACES], - }) - } - return enforced -} - -let enforcedSurfaces: ReadonlySet | undefined - -/** - * True when unrecorded provenance from this surface must fail the run rather than warn. - * - * Nothing is enforced by default. `unknown` provenance means "nobody recorded what secrets this - * value carries", which is the same thing a pre-tracking legacy row says — and legacy rows are read - * as carrying none. Enforcing one and not the other made an *aware* writer that momentarily could - * not vouch strictly worse than an unaware one: the row it wrote latched every run that later read - * it, and each latched run wrote more such rows, so a workspace could not recover without a data - * repair. - * - * Warning instead keeps that state visible and measurable while the writers that produce it are - * fixed. The sidecar still records `unknown` faithfully, so a surface can be closed back up once - * its writers stop losing provenance — and the rows that will start failing are countable from the - * sidecar table before the switch is thrown. This is a deliberate posture: an unenforced surface - * can under-redact a value whose provenance was lost. - * - * Set `DURABLE_SECRET_PROVENANCE_ENFORCED_SURFACES` to `all`, or to a comma-separated subset of - * {@link DURABLE_SECRET_PROVENANCE_SURFACES}, to close a surface. - * - * Workspace files were held out of this list while their fail-closed posture was its own decision. - * That decision arrived as broken state: because a file that was never tracked already reads as - * exact-empty, refusing only the file whose provenance could not be recorded made a writer that - * momentarily could not vouch permanently worse off than one that never tried — with no way back, - * since nothing rewrites a file's provenance but another content write. Live files across several - * workspaces sat unreadable behind it, by every tool route at once, carrying exactly as much - * information about their contents as the untracked files beside them: none. - */ -export function isDurableSecretProvenanceEnforced( - surface: DurableSecretProvenanceSurface -): boolean { - enforcedSurfaces ??= resolveEnforcedSurfaces() - return enforcedSurfaces.has(surface) -} - -/** - * What a surface could not vouch for. - * - * A closed union rather than a free-form string, for the reason the resolved-secret registry's - * reason set is one: a surface stays open on the strength of these lines trending to zero, and a - * cause that a call site can spell freely cannot be aggregated or alerted on. - */ -export type UnrecordedDurableProvenanceCause = - | 'durable-provenance-unknown' - | 'row-sidecar-not-exact' - | 'stored-memory-provenance-unknown' - -export interface UnrecordedDurableProvenanceReport { - surface: DurableSecretProvenanceSurface - cause: UnrecordedDurableProvenanceCause - /** How many records in this one read were unrecorded, when the caller reads a page at a time. */ - affectedCount?: number - workspaceId?: string - /** - * Whose access authorized the read. Null where the surface cannot name one — an audit row with - * no actor still carries the workspace, surface, and cause, which is what the trail is for. - */ - actorUserId?: string | null -} - -/** - * Records that a read proceeded on provenance nobody wrote down. - * - * Error, not warn, for the same reason the originating-fault reasons use it: error is the only - * level that survives every default the logger falls back to — production, test, and a self-hosted - * chart that sets no `LOG_LEVEL`. A surface stays open on the strength of this line being visible - * and trending to zero, so a level that a deployment can silently filter would leave the posture - * unmeasured. It is deliberately noisy on an affected workspace; that is the signal. - */ -export function reportUnrecordedDurableProvenance(report: UnrecordedDurableProvenanceReport): void { - logger.error('Proceeding on unrecorded durable secret provenance', { - surface: report.surface, - cause: report.cause, - enforced: false, - ...(report.affectedCount !== undefined ? { affectedCount: report.affectedCount } : {}), - ...(report.workspaceId ? { workspaceId: report.workspaceId } : {}), - }) - - /** - * The log line is for us; this is for the people who own the secrets. - * - * Recorded here rather than where provenance was lost, because losing it costs nothing on its - * own — a write nobody could vouch for is just data at rest. The exposure is this moment: a - * value crossing into a run that will project it to a model with no way to recognise a secret - * inside it and redact it. That is what a reader needs told, and it is why the entry names the - * surface and the count rather than a secret, which is precisely what was not recorded. - * - * Fire-and-forget by construction — `recordAudit` never throws — so the trail can never be the - * reason a run fails. Skipped without a workspace: the entry would name no one it concerns. - */ - if (!report.workspaceId) return - recordAudit({ - workspaceId: report.workspaceId, - actorId: report.actorUserId ?? null, - action: AuditAction.SECRET_PROVENANCE_UNRECORDED, - resourceType: AuditResourceType.SECRET_PROVENANCE, - resourceId: report.surface, - description: - 'A run read data whose secret provenance was never recorded, so any secret it carries could not be redacted before reaching a model.', - metadata: { - surface: report.surface, - cause: report.cause, - ...(report.affectedCount !== undefined ? { affectedCount: report.affectedCount } : {}), - }, - }) -} - -export type DurableSecretProvenanceWriteCause = - | 'source-provenance-unknown' - | 'invalid-provenance-entries' - | 'source-hash-unavailable' - | 'workspace-file-write-unknown' - | 'workspace-file-write-unrecorded' - -export interface DurableSecretProvenanceWriteReport { - surface: DurableSecretProvenanceSurface - status: 'unknown' | 'unrecorded' - cause: DurableSecretProvenanceWriteCause - recordCount?: number - workspaceId?: string - resourceId?: string -} - -export type DurableSecretProvenanceRefusalCause = - | 'knowledge-document-source-unavailable' - | 'knowledge-result-provenance-unavailable' - | 'knowledge-chunk-source-unavailable' - | 'knowledge-workspace-file-source-unavailable' - | 'workspace-file-provenance-unavailable' - | 'workspace-file-opaque-secret-content' - | 'workspace-file-registry-unavailable' - | 'workspace-file-unrecorded-enforced' - -export interface DurableSecretProvenanceRefusalReport { - surface: DurableSecretProvenanceSurface - cause: DurableSecretProvenanceRefusalCause - workspaceId?: string - resourceId?: string -} - -/** - * Reports a non-exact sidecar write without claiming its enclosing transaction committed. - * Kept separate from permissive-read telemetry so writer defects and affected reads can be counted - * independently. Callers report only tracked writes; ordinary legacy records are not a fault. - */ -export function reportDurableSecretProvenanceWrite( - report: DurableSecretProvenanceWriteReport -): void { - persistenceLogger.error('Writing non-exact durable secret provenance', { - surface: report.surface, - status: report.status, - cause: report.cause, - ...(report.recordCount !== undefined ? { recordCount: report.recordCount } : {}), - ...(report.workspaceId ? { workspaceId: report.workspaceId } : {}), - ...(report.resourceId ? { resourceId: report.resourceId } : {}), - }) -} - -/** Reports an existing refusal decision without changing the surface's compatibility policy. */ -export function reportDurableSecretProvenanceRefusal( - report: DurableSecretProvenanceRefusalReport -): void { - persistenceLogger.error('Refusing unavailable durable secret provenance', { - surface: report.surface, - cause: report.cause, - ...(report.workspaceId ? { workspaceId: report.workspaceId } : {}), - ...(report.resourceId ? { resourceId: report.resourceId } : {}), - }) -} - -/** Test seam: forces the next read to re-resolve the env-configured surfaces. */ -export function resetDurableSecretProvenanceEnforcementCache(): void { - enforcedSurfaces = undefined -} diff --git a/apps/sim/lib/execution/durable-secret-provenance-telemetry.test.ts b/apps/sim/lib/execution/durable-secret-provenance-telemetry.test.ts new file mode 100644 index 00000000000..bd319fa3db2 --- /dev/null +++ b/apps/sim/lib/execution/durable-secret-provenance-telemetry.test.ts @@ -0,0 +1,58 @@ +/** @vitest-environment node */ +import { beforeEach, describe, expect, it, vi } from 'vitest' + +const { logger } = vi.hoisted(() => ({ logger: { error: vi.fn() } })) +vi.mock('@sim/logger', () => ({ createLogger: () => logger })) + +import { + reportDurableSecretProvenanceRefusal, + reportDurableSecretProvenanceWrite, +} from '@/lib/execution/durable-secret-provenance-telemetry' + +describe('durable secret provenance telemetry', () => { + beforeEach(() => vi.clearAllMocks()) + it('reports non-exact writes without including private content or entries', () => { + const report = { + surface: 'knowledge' as const, + status: 'unknown' as const, + cause: 'source-provenance-unknown' as const, + recordCount: 2, + workspaceId: 'workspace-1', + resourceId: 'document-1', + content: 'private content', + entries: [{ encryptedValue: 'private ciphertext' }], + } + reportDurableSecretProvenanceWrite(report) + expect(logger.error).toHaveBeenCalledExactlyOnceWith( + 'Writing non-exact durable secret provenance', + { + surface: 'knowledge', + status: 'unknown', + cause: 'source-provenance-unknown', + recordCount: 2, + workspaceId: 'workspace-1', + resourceId: 'document-1', + } + ) + }) + it('reports a refusal without including private content or entries', () => { + const report = { + surface: 'workspace-file' as const, + cause: 'workspace-file-opaque-secret-content' as const, + workspaceId: 'workspace-1', + resourceId: 'file-1', + content: 'private content', + entries: [{ encryptedValue: 'private ciphertext' }], + } + reportDurableSecretProvenanceRefusal(report) + expect(logger.error).toHaveBeenCalledExactlyOnceWith( + 'Refusing unavailable durable secret provenance', + { + surface: 'workspace-file', + cause: 'workspace-file-opaque-secret-content', + workspaceId: 'workspace-1', + resourceId: 'file-1', + } + ) + }) +}) diff --git a/apps/sim/lib/execution/durable-secret-provenance-telemetry.ts b/apps/sim/lib/execution/durable-secret-provenance-telemetry.ts new file mode 100644 index 00000000000..bf77d2be246 --- /dev/null +++ b/apps/sim/lib/execution/durable-secret-provenance-telemetry.ts @@ -0,0 +1,67 @@ +import { createLogger } from '@sim/logger' + +const persistenceLogger = createLogger('DurableSecretProvenancePersistence') + +export type DurableSecretProvenanceSurface = 'memory' | 'table-row' | 'knowledge' | 'workspace-file' + +export type DurableSecretProvenanceWriteCause = + | 'source-provenance-unknown' + | 'invalid-provenance-entries' + | 'source-hash-unavailable' + | 'workspace-file-write-unknown' + | 'workspace-file-write-unrecorded' + +export interface DurableSecretProvenanceWriteReport { + surface: DurableSecretProvenanceSurface + status: 'unknown' | 'unrecorded' + cause: DurableSecretProvenanceWriteCause + recordCount?: number + workspaceId?: string + resourceId?: string +} + +export type DurableSecretProvenanceRefusalCause = + | 'knowledge-document-source-unavailable' + | 'knowledge-result-provenance-unavailable' + | 'knowledge-chunk-source-unavailable' + | 'knowledge-workspace-file-source-unavailable' + | 'workspace-file-provenance-unavailable' + | 'workspace-file-opaque-secret-content' + | 'workspace-file-registry-unavailable' + | 'workspace-file-unrecorded-enforced' + +export interface DurableSecretProvenanceRefusalReport { + surface: DurableSecretProvenanceSurface + cause: DurableSecretProvenanceRefusalCause + workspaceId?: string + resourceId?: string +} + +/** + * Reports a non-exact sidecar write without claiming its enclosing transaction committed. + * Writer defects and refused reads are reported separately. Ordinary legacy records are not faults. + */ +export function reportDurableSecretProvenanceWrite( + report: DurableSecretProvenanceWriteReport +): void { + persistenceLogger.error('Writing non-exact durable secret provenance', { + surface: report.surface, + status: report.status, + cause: report.cause, + ...(report.recordCount !== undefined ? { recordCount: report.recordCount } : {}), + ...(report.workspaceId ? { workspaceId: report.workspaceId } : {}), + ...(report.resourceId ? { resourceId: report.resourceId } : {}), + }) +} + +/** Reports a durable read refusal without exposing content or secret values. */ +export function reportDurableSecretProvenanceRefusal( + report: DurableSecretProvenanceRefusalReport +): void { + persistenceLogger.error('Refusing unavailable durable secret provenance', { + surface: report.surface, + cause: report.cause, + ...(report.workspaceId ? { workspaceId: report.workspaceId } : {}), + ...(report.resourceId ? { resourceId: report.resourceId } : {}), + }) +} diff --git a/apps/sim/lib/execution/durable-secret-provenance.test.ts b/apps/sim/lib/execution/durable-secret-provenance.test.ts index 3180c24db5c..26f48ed5f08 100644 --- a/apps/sim/lib/execution/durable-secret-provenance.test.ts +++ b/apps/sim/lib/execution/durable-secret-provenance.test.ts @@ -2,18 +2,6 @@ * @vitest-environment node */ import { beforeEach, describe, expect, it, vi } from 'vitest' - -const { mockIsEnforced, mockReport } = vi.hoisted(() => ({ - mockIsEnforced: vi.fn(() => false), - mockReport: vi.fn(), -})) - -vi.mock('@/lib/execution/durable-secret-provenance-enforcement', () => ({ - DURABLE_SECRET_PROVENANCE_SURFACES: ['memory', 'table-row', 'knowledge'], - isDurableSecretProvenanceEnforced: mockIsEnforced, - reportUnrecordedDurableProvenance: mockReport, -})) - import { durableSecretProvenanceFromPrivateBundle, filterDurableSecretProvenanceBySourceValues, @@ -275,37 +263,12 @@ describe('importing unrecorded durable provenance', () => { beforeEach(() => { vi.clearAllMocks() - mockIsEnforced.mockReturnValue(false) }) - it('warns and leaves the registry able to vouch when the surface is not enforced', async () => { + it('refuses unknown provenance at the shared import boundary', async () => { const registry = new ResolvedSecretTraceRegistry() - await expect( - importDurableSecretProvenance(registry, UNKNOWN, undefined, 'memory') - ).resolves.toBe(true) - expect(registry.isPermanentlyIncomplete()).toBe(false) - expect(mockReport).toHaveBeenCalledWith({ - surface: 'memory', - cause: 'durable-provenance-unknown', - }) - }) - - it('latches the registry once that surface is closed', async () => { - mockIsEnforced.mockReturnValue(true) - const registry = new ResolvedSecretTraceRegistry() - - await expect( - importDurableSecretProvenance(registry, UNKNOWN, undefined, 'memory') - ).resolves.toBe(false) - expect(registry.isPermanentlyIncomplete()).toBe(true) - expect(mockReport).not.toHaveBeenCalled() - }) - - it('latches for a caller that has not declared a surface', async () => { - const registry = new ResolvedSecretTraceRegistry() - - await expect(importDurableSecretProvenance(registry, UNKNOWN)).resolves.toBe(false) + await expect(importDurableSecretProvenance(registry, UNKNOWN, undefined)).resolves.toBe(false) expect(registry.isPermanentlyIncomplete()).toBe(true) }) @@ -313,10 +276,7 @@ describe('importing unrecorded durable provenance', () => { const registry = new ResolvedSecretTraceRegistry() const malformed = { status: 'exact', entries: [{ encryptedValue: '' }] } as never - await expect( - importDurableSecretProvenance(registry, malformed, undefined, 'memory') - ).resolves.toBe(false) + await expect(importDurableSecretProvenance(registry, malformed, undefined)).resolves.toBe(false) expect(registry.isPermanentlyIncomplete()).toBe(true) - expect(mockReport).not.toHaveBeenCalled() }) }) diff --git a/apps/sim/lib/execution/durable-secret-provenance.ts b/apps/sim/lib/execution/durable-secret-provenance.ts index 6fbdc24feed..2e7900dfd0c 100644 --- a/apps/sim/lib/execution/durable-secret-provenance.ts +++ b/apps/sim/lib/execution/durable-secret-provenance.ts @@ -1,10 +1,5 @@ import { createHash } from 'node:crypto' import type { DurableSecretProvenanceEntry } from '@sim/db/schema' -import { - type DurableSecretProvenanceSurface, - isDurableSecretProvenanceEnforced, - reportUnrecordedDurableProvenance, -} from '@/lib/execution/durable-secret-provenance-enforcement' import { isPrivateSecretProvenanceBundleV1, type PrivateSecretProvenanceBundleV1, @@ -205,35 +200,13 @@ export function filterDurableSecretProvenanceBySourceValues( return entries ? { status: 'exact', entries } : { status: 'unknown' } } -/** - * Imports durable entries into a model-bound registry, preserving source-scope anonymity. - * - * `surface` selects the enforcement policy for provenance nobody recorded. Omitting it enforces, - * which is the right default for a caller that has not been reviewed against - * {@link isDurableSecretProvenanceEnforced} yet. - */ +/** Imports exact durable entries into a model-bound registry, preserving source-scope anonymity. */ export async function importDurableSecretProvenance( registry: ResolvedSecretTraceRegistry, provenance: DurableSecretProvenance, - value?: unknown, - surface?: DurableSecretProvenanceSurface, - /** - * Set by a caller that reports the whole read itself. - * - * This function sees one record and knows no workspace, so its report can only ever be a log - * line, one per record. A caller reading a page can say the same thing once, with the workspace - * and the count — which is the entry that reaches the people who own the secrets. Both reporting - * would double-count the same event at two different granularities. - */ - options: { reportUnrecorded?: boolean } = {} + value?: unknown ): Promise { if (provenance.status === 'unknown') { - if (surface && !isDurableSecretProvenanceEnforced(surface)) { - if (options.reportUnrecorded !== false) { - reportUnrecordedDurableProvenance({ surface, cause: 'durable-provenance-unknown' }) - } - return true - } registry.markIncomplete('durable-provenance-unknown') return false } diff --git a/apps/sim/lib/function-execution/execute-request.ts b/apps/sim/lib/function-execution/execute-request.ts index de66f8dbfe9..af34bdf672e 100644 --- a/apps/sim/lib/function-execution/execute-request.ts +++ b/apps/sim/lib/function-execution/execute-request.ts @@ -1052,7 +1052,6 @@ async function importRuntimeFileContributors( workspaceId: context.workspaceId, identity, registry: context.runtimeFileSecretTraceRegistry, - actorUserId: context.fileAccessUserId, }) if (!imported) throw new Error('File secret provenance is unavailable for Function execution') } diff --git a/apps/sim/lib/internal/file/operations.provenance.test.ts b/apps/sim/lib/internal/file/operations.provenance.test.ts index e82ebb64904..c3741e17441 100644 --- a/apps/sim/lib/internal/file/operations.provenance.test.ts +++ b/apps/sim/lib/internal/file/operations.provenance.test.ts @@ -13,7 +13,6 @@ import { import { beforeEach, describe, expect, it, vi } from 'vitest' const { - mockEnforced, mockAssertActiveWorkspaceAccess, mockFetchWorkspaceFileBuffer, mockLoadActiveWorkspaceContext, @@ -23,7 +22,6 @@ const { mockResolveWorkspaceFileReference, mockUpdateWorkspaceFileContent, } = vi.hoisted(() => ({ - mockEnforced: vi.fn(() => false), mockAssertActiveWorkspaceAccess: vi.fn(), mockFetchWorkspaceFileBuffer: vi.fn(), mockLoadActiveWorkspaceContext: vi.fn(), @@ -164,9 +162,7 @@ vi.mock('@/app/api/files/authorization', () => ({ verifyFileAccess: vi.fn(), })) -vi.mock('@/lib/execution/durable-secret-provenance-enforcement', () => ({ - isDurableSecretProvenanceEnforced: mockEnforced, - reportUnrecordedDurableProvenance: vi.fn(), +vi.mock('@/lib/execution/durable-secret-provenance-telemetry', () => ({ reportDurableSecretProvenanceWrite: vi.fn(), reportDurableSecretProvenanceRefusal: vi.fn(), })) @@ -259,7 +255,6 @@ describe('appended file provenance', () => { beforeEach(() => { vi.clearAllMocks() resetDbChainMock() - mockEnforced.mockReturnValue(false) dbChainMockFns.returning.mockResolvedValue([{ id: 'file-1' }]) mockResolveEffectiveWorkspacePermission.mockResolvedValue('write') mockLoadActiveWorkspaceContext.mockResolvedValue({ @@ -366,35 +361,31 @@ describe('appended file provenance', () => { expect(persisted.contentUpdatedAt).toEqual(NEXT_CONTENT_UPDATED_AT) expect(persisted.entries).toHaveLength(expectedStatus === 'exact' && secret ? 1 : 0) expect(dbChainMockFns.set).toHaveBeenCalledWith({ secretProvenanceVersion: 1 }) - for (const enforced of [false, true]) { - mockEnforced.mockReturnValue(enforced) - queueTableRows(workspaceFiles, [ - joinedRow(persisted.status, persisted.entries, persisted.contentUpdatedAt), - ]) - const registry = new ResolvedSecretTraceRegistry([], SCOPE) - const permitted = await importWorkspaceFileSecretProvenanceForModelView({ - workspaceId: 'workspace-1', - identity: IDENTITY, - registry, - view: 'complete', - value: `before:${content}`, + + queueTableRows(workspaceFiles, [ + joinedRow(persisted.status, persisted.entries, persisted.contentUpdatedAt), + ]) + const registry = new ResolvedSecretTraceRegistry([], SCOPE) + const permitted = await importWorkspaceFileSecretProvenanceForModelView({ + workspaceId: 'workspace-1', + identity: IDENTITY, + registry, + view: 'complete', + value: `before:${content}`, + }) + expect(permitted).toBe(expectedStatus === 'exact') + if (permitted) { + expect(projectResolvedSecretModelContent(`before:${content}`, registry)).toEqual({ + safe: true, + value: secret ? 'before:{{TOKEN}}' : `before:${content}`, }) - expect(permitted).toBe( - expectedStatus === 'exact' || (expectedStatus === 'unrecorded' && !enforced) - ) - if (permitted) { - expect(projectResolvedSecretModelContent(`before:${content}`, registry)).toEqual({ - safe: true, - value: secret ? 'before:{{TOKEN}}' : `before:${content}`, - }) - } - queueTableRows(workspaceFiles, [ - joinedRow(persisted.status, persisted.entries, persisted.contentUpdatedAt), - ]) - expect(await isOpaqueWorkspaceFileEgressSafe('workspace-1', IDENTITY)).toBe( - (expectedStatus === 'exact' && !secret) || (expectedStatus === 'unrecorded' && !enforced) - ) } + queueTableRows(workspaceFiles, [ + joinedRow(persisted.status, persisted.entries, persisted.contentUpdatedAt), + ]) + expect(await isOpaqueWorkspaceFileEgressSafe('workspace-1', IDENTITY)).toBe( + expectedStatus === 'exact' && !secret + ) } ) }) @@ -419,20 +410,14 @@ describe('execution-file content provenance', () => { }) it.each([ - { status: 'exact', version: 1, stale: false, enforced: false, complete: true }, - { status: 'exact', version: 1, stale: false, enforced: true, complete: true }, - { status: 'unrecorded', version: 1, stale: false, enforced: false, complete: true }, - { status: 'unrecorded', version: 1, stale: false, enforced: true, complete: false }, - { status: 'unknown', version: 1, stale: false, enforced: false, complete: false }, - { status: 'unknown', version: 1, stale: false, enforced: true, complete: false }, - { status: 'unknown', version: null, stale: false, enforced: false, complete: true }, - { status: 'unknown', version: null, stale: false, enforced: true, complete: true }, - { status: 'exact', version: 1, stale: true, enforced: false, complete: false }, - { status: 'exact', version: 1, stale: true, enforced: true, complete: false }, + { status: 'exact', version: 1, stale: false, complete: true }, + { status: 'unrecorded', version: 1, stale: false, complete: false }, + { status: 'unknown', version: 1, stale: false, complete: false }, + { status: 'unknown', version: null, stale: false, complete: true }, + { status: 'exact', version: 1, stale: true, complete: false }, ])( - 'reads $status version=$version stale=$stale with enforcement=$enforced', - async ({ status, version, stale, enforced, complete }) => { - mockEnforced.mockReturnValue(enforced) + 'reads $status version=$version stale=$stale', + async ({ status, version, stale, complete }) => { queueTableRows(workspaceFiles, [ { ...joinedRow(status), @@ -450,7 +435,6 @@ describe('execution-file content provenance', () => { ) it('retains exact secret-bearing execution lineage for downstream text projections', async () => { - mockEnforced.mockReturnValue(true) queueTableRows(workspaceFiles, [ joinedRow('exact', [ { diff --git a/apps/sim/lib/internal/file/operations.ts b/apps/sim/lib/internal/file/operations.ts index 5e84bdcac74..ece36d9e054 100644 --- a/apps/sim/lib/internal/file/operations.ts +++ b/apps/sim/lib/internal/file/operations.ts @@ -46,7 +46,6 @@ import type { } from '@/lib/uploads/contexts/workspace/workspace-file-manager' import { getBoundWorkspaceFileSecretProvenance, - mayReadUnrecordedWorkspaceFile, mergeWorkspaceFileSecretProvenance, type WorkspaceFileSecretProvenance, type WorkspaceFileSecretProvenanceIdentity, @@ -620,7 +619,6 @@ export async function getFileContentProvenance( } const provenance = await readFileSourceSecretProvenance(principal, workspaceId, source.identity) signal?.throwIfAborted() - if (provenance.status === 'unrecorded' && mayReadUnrecordedWorkspaceFile(workspaceId)) continue if (provenance.status !== 'exact' || (source.opaque && provenance.entries.length > 0)) { accumulator.markIncomplete('workspace-file-provenance-unknown') continue diff --git a/apps/sim/lib/internal/memory/provenance.ts b/apps/sim/lib/internal/memory/provenance.ts index 788186ecef4..fa8722c4ccc 100644 --- a/apps/sim/lib/internal/memory/provenance.ts +++ b/apps/sim/lib/internal/memory/provenance.ts @@ -67,9 +67,7 @@ export async function createMemoryToolResponse( const registry = new ResolvedSecretTraceRegistry([], scope) for (const item of provenance) { - await importDurableSecretProvenance(registry, item.provenance, item.data, 'memory', { - reportUnrecorded: false, - }) + await importDurableSecretProvenance(registry, item.provenance, item.data) } const envelope = serializePrivateToolMetadataResponseEnvelope( body, diff --git a/apps/sim/lib/knowledge/__integration__/external-file-provenance.integration.ts b/apps/sim/lib/knowledge/__integration__/external-file-provenance.integration.ts index df6b7ca6b22..e4bd466e9e9 100644 --- a/apps/sim/lib/knowledge/__integration__/external-file-provenance.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/external-file-provenance.integration.ts @@ -11,7 +11,6 @@ import { eq, inArray } from 'drizzle-orm' import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' const fixtureStorage = vi.hoisted(() => { - process.env.DURABLE_SECRET_PROVENANCE_ENFORCED_SURFACES = 'workspace-file' return { root: '' } }) vi.mock('@/lib/uploads/core/setup.server', () => ({ @@ -24,7 +23,6 @@ import { fileParseBodySchema } from '@/lib/api/contracts/storage-transfer' import { encryptSecret } from '@/lib/core/security/encryption' import * as inputValidation from '@/lib/core/security/input-validation.server' import { isUserFileWithMetadata } from '@/lib/core/utils/user-file' -import { isDurableSecretProvenanceEnforced } from '@/lib/execution/durable-secret-provenance-enforcement' import { executeFileParserOperation } from '@/lib/internal/file/parser' import { createKnowledgeAclFixtureIds, @@ -108,7 +106,6 @@ async function identityFor(file: UserFile): Promise { fixtureStorage.root = mkdtempSync(path.join(tmpdir(), 'sim-external-file-provenance-')) - expect(isDurableSecretProvenanceEnforced('workspace-file')).toBe(true) }) beforeEach(() => { fetchSpy.mockReset() diff --git a/apps/sim/lib/knowledge/__integration__/upload-read-provenance.integration.ts b/apps/sim/lib/knowledge/__integration__/upload-read-provenance.integration.ts index 54db3536ac7..a602cf272f6 100644 --- a/apps/sim/lib/knowledge/__integration__/upload-read-provenance.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/upload-read-provenance.integration.ts @@ -136,7 +136,6 @@ describe('chat upload reads racing with save_upload', () => { expect( await importWorkspaceFileSecretProvenanceForModelView({ workspaceId: ids.workspaceId, - actorUserId: ids.aliceId, identity: envelope.file, value: envelope.value, view: 'opaque', @@ -202,7 +201,6 @@ describe('chat upload reads racing with save_upload', () => { expect( await importWorkspaceFileSecretProvenanceForModelView({ workspaceId: ids.workspaceId, - actorUserId: ids.aliceId, identity: read.file, value: read.value, view: 'opaque', diff --git a/apps/sim/lib/knowledge/api/secret-provenance.ts b/apps/sim/lib/knowledge/api/secret-provenance.ts index 4ca15750404..29935ee84d5 100644 --- a/apps/sim/lib/knowledge/api/secret-provenance.ts +++ b/apps/sim/lib/knowledge/api/secret-provenance.ts @@ -243,8 +243,6 @@ export async function finalizeKnowledgePersistedResponse(options: { registry, documents: options.documents, chunks: options.chunks, - ...(options.workspaceId ? { workspaceId: options.workspaceId } : {}), - actorUserId: options.userId, }) return finalizeKnowledgeRegistryResponse({ headers: options.headers, diff --git a/apps/sim/lib/knowledge/application/add-workspace-files.ts b/apps/sim/lib/knowledge/application/add-workspace-files.ts index 0e458afd603..b4822bf8a2f 100644 --- a/apps/sim/lib/knowledge/application/add-workspace-files.ts +++ b/apps/sim/lib/knowledge/application/add-workspace-files.ts @@ -5,7 +5,7 @@ import { checkAttributedUsageLimits } from '@/lib/billing/core/billing-attributi import { authorizeWorkspaceOperation } from '@/lib/core/application' import { asOrchestrationError, OrchestrationError } from '@/lib/core/orchestration/types' import { generateRequestId } from '@/lib/core/utils/request' -import { reportDurableSecretProvenanceRefusal } from '@/lib/execution/durable-secret-provenance-enforcement' +import { reportDurableSecretProvenanceRefusal } from '@/lib/execution/durable-secret-provenance-telemetry' import { PROVENANCE_MAX_ENTRIES } from '@/lib/execution/provenance-limits' import { knowledgeDelegationPolicy } from '@/lib/knowledge/application/authorization' import { defineAuthorizedKnowledgeUseCase } from '@/lib/knowledge/application/authorized-knowledge-use-case' diff --git a/apps/sim/lib/knowledge/application/chunks.ts b/apps/sim/lib/knowledge/application/chunks.ts index 6e5822856aa..fd2f5774014 100644 --- a/apps/sim/lib/knowledge/application/chunks.ts +++ b/apps/sim/lib/knowledge/application/chunks.ts @@ -7,7 +7,7 @@ import { createDurableSecretProvenanceRegistry, type DurableSecretProvenance, } from '@/lib/execution/durable-secret-provenance' -import { reportDurableSecretProvenanceRefusal } from '@/lib/execution/durable-secret-provenance-enforcement' +import { reportDurableSecretProvenanceRefusal } from '@/lib/execution/durable-secret-provenance-telemetry' import { defineAuthorizedKnowledgeUseCase } from '@/lib/knowledge/application/authorized-knowledge-use-case' import { resolveKnowledgeAttributedUserId } from '@/lib/knowledge/application/billing' import { KnowledgeDocumentNotReadyError } from '@/lib/knowledge/application/chunk-errors' diff --git a/apps/sim/lib/knowledge/application/read-indexed-document.ts b/apps/sim/lib/knowledge/application/read-indexed-document.ts index 37acf6579b2..d5f2106597a 100644 --- a/apps/sim/lib/knowledge/application/read-indexed-document.ts +++ b/apps/sim/lib/knowledge/application/read-indexed-document.ts @@ -1,4 +1,3 @@ -import { resolvePrincipalSubjectUserId } from '@sim/auth/principal' import { db } from '@sim/db' import { document, embedding } from '@sim/db/schema' import { and, eq, isNull, lte, sql } from 'drizzle-orm' @@ -161,13 +160,9 @@ export const readIndexedKnowledgeDocument = defineAuthorizedKnowledgeUseCase({ connectorType: doc.connectorType, processingStatus: doc.processingStatus, } - const provenanceContext = { - registry: input.resultSecretRegistry, - actorUserId: resolvePrincipalSubjectUserId(principal) ?? undefined, - } if ( !(await importKnowledgePersistedResponseSecretProvenance({ - ...provenanceContext, + registry: input.resultSecretRegistry, documents: [{ id: doc.id, source: createKnowledgeDocumentSourceValue(doc), value }], })) ) { @@ -221,7 +216,7 @@ export const readIndexedKnowledgeDocument = defineAuthorizedKnowledgeUseCase({ const chunks = page.chunks.map(({ id, chunkIndex, content }) => ({ id, chunkIndex, content })) if ( !(await importKnowledgePersistedResponseSecretProvenance({ - ...provenanceContext, + registry: input.resultSecretRegistry, chunks: chunks.map((chunk) => ({ ...chunk, documentId, value: chunk })), })) ) { diff --git a/apps/sim/lib/knowledge/application/read-search-document.ts b/apps/sim/lib/knowledge/application/read-search-document.ts index dbc52ccc0b5..94aef40c21f 100644 --- a/apps/sim/lib/knowledge/application/read-search-document.ts +++ b/apps/sim/lib/knowledge/application/read-search-document.ts @@ -115,12 +115,10 @@ export const readSearchDocument = defineAuthorizedKnowledgeUseCase({ const metadata = provenance.documentMetadata[context.documentId] if ( metadata && - !(await importDurableSecretProvenance( - input.resultSecretRegistry, - metadata.provenance, - { documentName: metadata.filename, sourceUrl: metadata.sourceUrl }, - 'knowledge' - )) + !(await importDurableSecretProvenance(input.resultSecretRegistry, metadata.provenance, { + documentName: metadata.filename, + sourceUrl: metadata.sourceUrl, + })) ) { throw new Error('Knowledge document provenance is unavailable') } diff --git a/apps/sim/lib/knowledge/application/search.ts b/apps/sim/lib/knowledge/application/search.ts index 1b7c2adcf40..5b186cc1f22 100644 --- a/apps/sim/lib/knowledge/application/search.ts +++ b/apps/sim/lib/knowledge/application/search.ts @@ -13,11 +13,7 @@ import { resourceScopeFromOwner, resourceScopeKey } from '@/lib/core/resource-sc import { PlatformEvents } from '@/lib/core/telemetry' import { generateRequestId } from '@/lib/core/utils/request' import { importDurableSecretProvenance } from '@/lib/execution/durable-secret-provenance' -import { - isDurableSecretProvenanceEnforced, - reportDurableSecretProvenanceRefusal, - reportUnrecordedDurableProvenance, -} from '@/lib/execution/durable-secret-provenance-enforcement' +import { reportDurableSecretProvenanceRefusal } from '@/lib/execution/durable-secret-provenance-telemetry' import { requireOrganizationSearchAvailable } from '@/lib/knowledge/access/availability' import { createKnowledgeAccessProvider } from '@/lib/knowledge/access/scope' import type { KnowledgeAccessProvider } from '@/lib/knowledge/access/types' @@ -550,8 +546,6 @@ const searchKnowledgeUseCase = defineAuthorizedKnowledgeUseCase({ status: rerankerStatus, candidateCount, resultCount: rows.length, - unrecordedChunkCount: provenanceSnapshot?.unrecordedCount ?? 0, - enforced: isDurableSecretProvenanceEnforced('knowledge'), workspaceId: context.workspaceId, }) } else if (useReranker) { @@ -670,8 +664,6 @@ const searchKnowledgeUseCase = defineAuthorizedKnowledgeUseCase({ } }) if (registry && provenanceSnapshot) { - const knowledgeEnforced = isDurableSecretProvenanceEnforced('knowledge') - let unrecordedCount = provenanceSnapshot.unrecordedCount for (const [documentId, document] of Object.entries(provenanceSnapshot.documentMetadata)) { const renderedMetadata = results .filter((result) => result.documentId === documentId) @@ -681,36 +673,14 @@ const searchKnowledgeUseCase = defineAuthorizedKnowledgeUseCase({ metadata: result.metadata, })) if (renderedMetadata.length === 0) continue - if (document.provenance.status === 'unknown' && !knowledgeEnforced) unrecordedCount += 1 if ( !(await measureSearchStage('metadata_provenance', () => - importDurableSecretProvenance( - registry, - document.provenance, - renderedMetadata, - 'knowledge', - { reportUnrecorded: false } - ) + importDurableSecretProvenance(registry, document.provenance, renderedMetadata) )) ) { registry.markIncomplete('knowledge-result-provenance-unavailable') } } - /** - * One entry for the whole search — chunks and rendered metadata are one read. Skipped when - * the registry latched: a latched read never reaches a model, and this entry exists to say a - * fail-open read went ahead unvouched. - */ - if (unrecordedCount > 0 && !registry.isPermanentlyIncomplete()) { - reportUnrecordedDurableProvenance({ - surface: 'knowledge', - cause: 'durable-provenance-unknown', - affectedCount: unrecordedCount, - workspaceId: context.workspaceId, - - actorUserId: userId, - }) - } } annotateSearchDiagnostics({ resultCount: results.length }) const cost = baseCost diff --git a/apps/sim/lib/knowledge/secret-provenance.test.ts b/apps/sim/lib/knowledge/secret-provenance.test.ts index 4236fe27e66..2b5b06de981 100644 --- a/apps/sim/lib/knowledge/secret-provenance.test.ts +++ b/apps/sim/lib/knowledge/secret-provenance.test.ts @@ -19,22 +19,17 @@ import { } from '@/lib/knowledge/secret-provenance' import { ResolvedSecretTraceRegistry } from '@/executor/utils/resolved-secret-trace-registry' -const { mockDecryptSecret, mockIsEnforced, mockReport, mockReportWrite, mockReportRefusal } = - vi.hoisted(() => ({ - mockDecryptSecret: vi.fn(), - mockIsEnforced: vi.fn(() => false), - mockReport: vi.fn(), - mockReportWrite: vi.fn(), - mockReportRefusal: vi.fn(), - })) +const { mockDecryptSecret, mockReportWrite, mockReportRefusal } = vi.hoisted(() => ({ + mockDecryptSecret: vi.fn(), + mockReportWrite: vi.fn(), + mockReportRefusal: vi.fn(), +})) vi.mock('@/lib/core/security/encryption', () => ({ decryptSecret: mockDecryptSecret, })) -vi.mock('@/lib/execution/durable-secret-provenance-enforcement', () => ({ - isDurableSecretProvenanceEnforced: mockIsEnforced, - reportUnrecordedDurableProvenance: mockReport, +vi.mock('@/lib/execution/durable-secret-provenance-telemetry', () => ({ reportDurableSecretProvenanceWrite: mockReportWrite, reportDurableSecretProvenanceRefusal: mockReportRefusal, })) @@ -59,7 +54,6 @@ describe('knowledge durable secret provenance', () => { resetDbChainMock() queueTableRows(document, [DOCUMENT_ROW]) mockDecryptSecret.mockResolvedValue({ decrypted: 'tracked-secret' }) - mockIsEnforced.mockReturnValue(false) }) it('uses the same explicit source shape for joined rows and persisted writes', () => { @@ -189,7 +183,7 @@ describe('knowledge durable secret provenance', () => { }) }) -describe('knowledge unrecorded-read reporting', () => { +describe('knowledge durable provenance enforcement', () => { const SCOPE = { userId: 'user-1', workspaceId: 'workspace-1' } const UNRECORDED_DOCUMENT_ROW = { id: 'doc-1', @@ -213,10 +207,9 @@ describe('knowledge unrecorded-read reporting', () => { beforeEach(() => { vi.clearAllMocks() resetDbChainMock() - mockIsEnforced.mockReturnValue(false) }) - it('reports one aggregated entry per read, naming workspace, actor, and count', async () => { + it('refuses unknown document provenance in a mixed persisted response', async () => { queueTableRows(document, [UNRECORDED_DOCUMENT_ROW]) queueTableRows(embedding, [UNRECORDED_CHUNK_ROW]) const registry = new ResolvedSecretTraceRegistry([], SCOPE) @@ -226,24 +219,13 @@ describe('knowledge unrecorded-read reporting', () => { registry, documents: [{ id: 'doc-1', source: DOCUMENT_SOURCE, value: {} }], chunks: [{ id: 'chunk-1', documentId: 'doc-1', content: 'chunk text', value: {} }], - workspaceId: 'workspace-1', - actorUserId: 'user-1', }) - ).resolves.toBe(true) + ).resolves.toBe(false) - expect(registry.isPermanentlyIncomplete()).toBe(false) - expect(mockReport).toHaveBeenCalledTimes(1) - expect(mockReport).toHaveBeenCalledWith({ - surface: 'knowledge', - cause: 'durable-provenance-unknown', - affectedCount: 2, - workspaceId: 'workspace-1', - actorUserId: 'user-1', - }) + expect(registry.isPermanentlyIncomplete()).toBe(true) }) - /** A fault return fails the read closed, so no unvouched record reached anything to report. */ - it('reports nothing when the read fails closed on a missing row', async () => { + it('refuses missing persisted rows', async () => { queueTableRows(document, []) const registry = new ResolvedSecretTraceRegistry([], SCOPE) @@ -251,16 +233,11 @@ describe('knowledge unrecorded-read reporting', () => { importKnowledgePersistedResponseSecretProvenance({ registry, documents: [{ id: 'doc-1', source: DOCUMENT_SOURCE, value: {} }], - workspaceId: 'workspace-1', - actorUserId: 'user-1', }) ).resolves.toBe(false) - - expect(mockReport).not.toHaveBeenCalled() }) - it('latches without reporting once the surface is enforced', async () => { - mockIsEnforced.mockReturnValue(true) + it('refuses unknown document provenance', async () => { queueTableRows(document, [UNRECORDED_DOCUMENT_ROW]) const registry = new ResolvedSecretTraceRegistry([], SCOPE) @@ -268,17 +245,13 @@ describe('knowledge unrecorded-read reporting', () => { importKnowledgePersistedResponseSecretProvenance({ registry, documents: [{ id: 'doc-1', source: DOCUMENT_SOURCE, value: {} }], - workspaceId: 'workspace-1', - actorUserId: 'user-1', }) ).resolves.toBe(false) expect(registry.isPermanentlyIncomplete()).toBe(true) - expect(mockReport).not.toHaveBeenCalled() }) - /** The search read spans chunks and rendered metadata, so its caller owns the one report. */ - it('returns the unrecorded count from a search import instead of reporting it', async () => { + it('refuses unrecorded chunks during a search import', async () => { queueTableRows(embedding, [{ ...UNRECORDED_CHUNK_ROW, documentId: DOCUMENT_ROW.id }]) queueTableRows(document, [DOCUMENT_ROW]) const registry = new ResolvedSecretTraceRegistry([], SCOPE) @@ -288,8 +261,7 @@ describe('knowledge unrecorded-read reporting', () => { results: [{ id: 'chunk-1', documentId: DOCUMENT_ROW.id, content: 'chunk text' }], }) - expect(snapshot.imported).toBe(true) - expect(snapshot.unrecordedCount).toBe(1) - expect(mockReport).not.toHaveBeenCalled() + expect(snapshot.imported).toBe(false) + expect(registry.isPermanentlyIncomplete()).toBe(true) }) }) diff --git a/apps/sim/lib/knowledge/secret-provenance.ts b/apps/sim/lib/knowledge/secret-provenance.ts index f2fff65491d..3f73183cb46 100644 --- a/apps/sim/lib/knowledge/secret-provenance.ts +++ b/apps/sim/lib/knowledge/secret-provenance.ts @@ -19,11 +19,9 @@ import { normalizeDurableSecretProvenanceEntries, } from '@/lib/execution/durable-secret-provenance' import { - isDurableSecretProvenanceEnforced, reportDurableSecretProvenanceRefusal, reportDurableSecretProvenanceWrite, - reportUnrecordedDurableProvenance, -} from '@/lib/execution/durable-secret-provenance-enforcement' +} from '@/lib/execution/durable-secret-provenance-telemetry' import { ResolvedSecretTraceRegistry, type ResolvedSecretTraceScopeV1, @@ -435,7 +433,7 @@ export async function loadKnowledgeDocumentSecretRegistry( tracked: row.secretProvenanceVersion === 1 || currentSourceFileProvenance !== undefined, } const registry = new ResolvedSecretTraceRegistry([], scope) - if (!(await importDurableSecretProvenance(registry, provenance, undefined, 'knowledge'))) { + if (!(await importDurableSecretProvenance(registry, provenance, undefined))) { throw new Error('Knowledge document secret provenance is unavailable') } return { registry, provenance, tracked: true } @@ -475,21 +473,9 @@ export async function importKnowledgePersistedResponseSecretProvenance(options: content: string value: unknown }[] - /** Names the workspace in the aggregated unrecorded-read audit entry; legacy KBs have none. */ - workspaceId?: string - /** Whose access authorized the read, for the same entry. */ - actorUserId?: string }): Promise { const documents = options.documents ?? [] const chunks = options.chunks ?? [] - /** - * Counted here and reported once at the end of the proceed path, the shape the memory and table - * surfaces use: the per-record import knows no workspace, so its report never produced the - * workspace-visible audit entry, and it logged once per record. A fault return skips the report — - * that read fails closed, so no unvouched record reached anything. - */ - const knowledgeEnforced = isDurableSecretProvenanceEnforced('knowledge') - let unrecordedCount = 0 const documentIds = [...new Set(documents.map((item) => item.id))] const chunkIds = [...new Set(chunks.map((item) => item.id))] const [documentRows, chunkRows] = await Promise.all([ @@ -531,12 +517,7 @@ export async function importKnowledgePersistedResponseSecretProvenance(options: readBoundKnowledgeDocumentSecretProvenance({ ...row, source }), source ) - if (provenance.status === 'unknown' && !knowledgeEnforced) unrecordedCount += 1 - if ( - !(await importDurableSecretProvenance(options.registry, provenance, item.value, 'knowledge', { - reportUnrecorded: false, - })) - ) { + if (!(await importDurableSecretProvenance(options.registry, provenance, item.value))) { return false } } @@ -548,25 +529,11 @@ export async function importKnowledgePersistedResponseSecretProvenance(options: return false } const provenance = readBoundKnowledgeEmbeddingSecretProvenance(row) - if (provenance.status === 'unknown' && !knowledgeEnforced) unrecordedCount += 1 - if ( - !(await importDurableSecretProvenance(options.registry, provenance, item.value, 'knowledge', { - reportUnrecorded: false, - })) - ) { + if (!(await importDurableSecretProvenance(options.registry, provenance, item.value))) { return false } } - if (unrecordedCount > 0) { - reportUnrecordedDurableProvenance({ - surface: 'knowledge', - cause: 'durable-provenance-unknown', - affectedCount: unrecordedCount, - ...(options.workspaceId ? { workspaceId: options.workspaceId } : {}), - actorUserId: options.actorUserId ?? null, - }) - } return !options.registry.isPermanentlyIncomplete() } @@ -576,13 +543,6 @@ export async function importKnowledgeSearchResultSecretProvenance(options: { results: readonly { id: string; documentId: string; content: string }[] }): Promise<{ imported: boolean - /** - * Chunks whose stored provenance was unrecorded and whose import proceeded fail-open. The caller - * folds this into one read-level audit report — it owns the workspace and the metadata imports - * that share the same read, and it reports nothing when the registry latched, since a latched - * read never reaches a model. - */ - unrecordedCount: number documentMetadata: Record< string, { @@ -600,7 +560,7 @@ export async function importKnowledgeSearchResultSecretProvenance(options: { > }> { if (options.results.length === 0) { - return { imported: true, unrecordedCount: 0, documentMetadata: {} } + return { imported: true, documentMetadata: {} } } const embeddingIds = [...new Set(options.results.map((result) => result.id))] const documentIds = [...new Set(options.results.map((result) => result.documentId))] @@ -617,35 +577,24 @@ export async function importKnowledgeSearchResultSecretProvenance(options: { ]) const chunkById = new Map(chunks.map((row) => [row.id, row])) if (chunkById.size !== embeddingIds.length) { - return { imported: false, unrecordedCount: 0, documentMetadata: {} } + return { imported: false, documentMetadata: {} } } - const knowledgeEnforced = isDurableSecretProvenanceEnforced('knowledge') - let unrecordedCount = 0 for (const result of options.results) { const row = chunkById.get(result.id) if (!row || row.documentId !== result.documentId || row.content !== result.content) { - return { imported: false, unrecordedCount: 0, documentMetadata: {} } + return { imported: false, documentMetadata: {} } } const provenance = readBoundKnowledgeEmbeddingSecretProvenance(row) - if (provenance.status === 'unknown' && !knowledgeEnforced) unrecordedCount += 1 - if ( - !(await importDurableSecretProvenance( - options.registry, - provenance, - result.content, - 'knowledge', - { reportUnrecorded: false } - )) - ) { - return { imported: false, unrecordedCount: 0, documentMetadata: {} } + if (!(await importDurableSecretProvenance(options.registry, provenance, result.content))) { + return { imported: false, documentMetadata: {} } } } const documentById = new Map(documents.map((row) => [row.id, row])) if (documentById.size !== documentIds.length) { - return { imported: false, unrecordedCount: 0, documentMetadata: {} } + return { imported: false, documentMetadata: {} } } if (options.results.some((result) => !documentById.has(result.documentId))) { - return { imported: false, unrecordedCount: 0, documentMetadata: {} } + return { imported: false, documentMetadata: {} } } const documentMetadata: Record< string, @@ -684,7 +633,6 @@ export async function importKnowledgeSearchResultSecretProvenance(options: { } return { imported: !options.registry.isPermanentlyIncomplete(), - unrecordedCount, documentMetadata, } } diff --git a/apps/sim/lib/memory/application/use-cases.test.ts b/apps/sim/lib/memory/application/use-cases.test.ts index f30c95699ba..585db69d2bb 100644 --- a/apps/sim/lib/memory/application/use-cases.test.ts +++ b/apps/sim/lib/memory/application/use-cases.test.ts @@ -10,7 +10,6 @@ import type { BillingAttributionSnapshot } from '@/lib/billing/core/billing-attr const mocks = vi.hoisted(() => ({ loadWorkspace: vi.fn(), resolvePermission: vi.fn(), - reportUnrecorded: vi.fn(), readBoundProvenance: vi.fn(), })) @@ -30,11 +29,6 @@ vi.mock('@/lib/billing/core/billing-attribution', () => ({ assertBillingAttributionSnapshot: (value: unknown) => value, })) -vi.mock('@/lib/execution/durable-secret-provenance-enforcement', () => ({ - isDurableSecretProvenanceEnforced: () => false, - reportUnrecordedDurableProvenance: mocks.reportUnrecorded, -})) - vi.mock('@/lib/memory/secret-provenance', () => ({ readBoundMemorySecretProvenance: mocks.readBoundProvenance, replaceMemorySecretProvenanceInTx: vi.fn(), @@ -130,13 +124,6 @@ describe('Memory application use cases', () => { userId: BILLING_OWNER_ID, workspaceId: WORKSPACE_ID, }) - expect(mocks.reportUnrecorded).toHaveBeenCalledWith({ - surface: 'memory', - cause: 'durable-provenance-unknown', - affectedCount: 1, - workspaceId: WORKSPACE_ID, - actorUserId: BILLING_OWNER_ID, - }) }) it('rejects billing attribution outside the authorized canonical workspace', async () => { @@ -170,6 +157,5 @@ describe('Memory application use cases', () => { resolveBillingAttribution.mock.invocationCallOrder[0] ) expect(mocks.readBoundProvenance).not.toHaveBeenCalled() - expect(mocks.reportUnrecorded).not.toHaveBeenCalled() }) }) diff --git a/apps/sim/lib/memory/application/use-cases.ts b/apps/sim/lib/memory/application/use-cases.ts index f7d57f561d9..c9e94e4f3e8 100644 --- a/apps/sim/lib/memory/application/use-cases.ts +++ b/apps/sim/lib/memory/application/use-cases.ts @@ -16,10 +16,6 @@ import { type DurableSecretProvenance, mergeDurableSecretProvenance, } from '@/lib/execution/durable-secret-provenance' -import { - isDurableSecretProvenanceEnforced, - reportUnrecordedDurableProvenance, -} from '@/lib/execution/durable-secret-provenance-enforcement' import { memoryDelegationPolicy } from '@/lib/memory/application/authorization' import { memoryOperations } from '@/lib/memory/application/operations' import { lockMemoryConversationInTx } from '@/lib/memory/locks' @@ -99,7 +95,6 @@ function memoryMessageError(data: unknown): string | null { async function loadReadProvenance( records: MemoryRecord[], - scope: MemoryLegacyProvenanceScope, signal?: AbortSignal ): Promise { if (records.length === 0) return [] @@ -113,8 +108,6 @@ async function loadReadProvenance( const result: MemoryReadProvenance[] = [] const ids = [...recordsById.keys()] - const enforced = isDurableSecretProvenanceEnforced('memory') - let unrecordedCount = 0 for (let index = 0; index < ids.length; index += PRIVATE_MEMORY_QUERY_CHUNK_SIZE) { signal?.throwIfAborted() @@ -135,22 +128,11 @@ async function loadReadProvenance( status: sidecar?.status ?? null, entries: sidecar?.entries, }) - if (provenance.status === 'unknown' && !enforced) unrecordedCount += 1 result.push({ data: record.data, provenance }) } } } - if (unrecordedCount > 0) { - reportUnrecordedDurableProvenance({ - surface: 'memory', - cause: 'durable-provenance-unknown', - affectedCount: unrecordedCount, - workspaceId: scope.workspaceId, - actorUserId: scope.userId, - }) - } - return result } @@ -173,7 +155,7 @@ async function readResultProvenance( input.resolveBillingAttribution )) return { - readProvenance: await loadReadProvenance(records, provenanceScope, input.signal), + readProvenance: await loadReadProvenance(records, input.signal), provenanceScope, } } diff --git a/apps/sim/lib/memory/message-provenance.postgres.test.ts b/apps/sim/lib/memory/message-provenance.postgres.test.ts index e675e8dd343..3fa1fc2b69b 100644 --- a/apps/sim/lib/memory/message-provenance.postgres.test.ts +++ b/apps/sim/lib/memory/message-provenance.postgres.test.ts @@ -10,9 +10,8 @@ import { drizzle, type PostgresJsDatabase } from 'drizzle-orm/postgres-js' import postgres from 'postgres' import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' -const { database, mockIsEnforced } = vi.hoisted(() => ({ +const { database } = vi.hoisted(() => ({ database: { current: undefined as PostgresJsDatabase | undefined }, - mockIsEnforced: vi.fn(), })) vi.unmock('drizzle-orm') @@ -32,10 +31,6 @@ vi.mock('@sim/db', () => ({ vi.mock('@/lib/core/security/encryption', () => ({ decryptSecret: async (value: string) => ({ decrypted: value.replace('cipher-', 'secret-') }), })) -vi.mock('@/lib/execution/durable-secret-provenance-enforcement', () => ({ - isDurableSecretProvenanceEnforced: mockIsEnforced, - reportUnrecordedDurableProvenance: vi.fn(), -})) vi.mock('@/lib/logs/execution/pii-redaction', () => ({ redactObjectStrings: async (value: unknown) => value, })) @@ -189,11 +184,10 @@ describe.skipIf(!databaseUrl)('memory provenance in PostgreSQL', () => { } }) - describe.each([false, true])('enforcement %s', (enforced) => { + describe('enforced memory provenance', () => { it('keeps a large one-secret conversation exact across tool and native writes and model reads', async () => { if (!connection) throw new Error('PostgreSQL test database is not initialized') - mockIsEnforced.mockReturnValue(enforced) - const key = `large-conversation-${enforced}` + const key = `large-conversation-strict` const messages = Array.from({ length: 17_000 }, (_, index) => ({ role: 'user', content: `secret-SHARED message-${index}`, @@ -246,9 +240,8 @@ describe.skipIf(!databaseUrl)('memory provenance in PostgreSQL', () => { 'preserves both first appends and secret bindings for %s', async (mode) => { if (!connection) throw new Error('PostgreSQL test database is not initialized') - mockIsEnforced.mockReturnValue(enforced) for (let index = 0; index < 8; index++) { - const key = `${enforced}-${mode}-${index}` + const key = `strict-${mode}-${index}` await Promise.all([ mode === 'native-native' ? nativeAppend(key, 'A') : toolAppend(key, 'A'), mode === 'tool-tool' ? toolAppend(key, 'B') : nativeAppend(key, 'B'), diff --git a/apps/sim/lib/memory/message-provenance.test.ts b/apps/sim/lib/memory/message-provenance.test.ts index af5d95e202a..554d9356bc0 100644 --- a/apps/sim/lib/memory/message-provenance.test.ts +++ b/apps/sim/lib/memory/message-provenance.test.ts @@ -9,18 +9,12 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' const mocks = vi.hoisted(() => ({ decrypt: vi.fn(), - isEnforced: vi.fn(), - report: vi.fn(), logger: { error: vi.fn(), warn: vi.fn(), info: vi.fn(), debug: vi.fn() }, loadWorkspace: vi.fn(), })) vi.mock('@sim/logger', () => ({ createLogger: () => mocks.logger })) vi.mock('@/lib/core/security/encryption', () => ({ decryptSecret: mocks.decrypt })) -vi.mock('@/lib/execution/durable-secret-provenance-enforcement', () => ({ - isDurableSecretProvenanceEnforced: mocks.isEnforced, - reportUnrecordedDurableProvenance: mocks.report, -})) vi.mock('@/lib/logs/execution/pii-redaction', () => ({ redactObjectStrings: vi.fn(async (value: unknown) => value), })) @@ -125,11 +119,10 @@ function principal(): WorkflowExecutionDelegatedPrincipal { } } -describe.each([false, true])('memory message provenance with enforcement %s', (enforced) => { +describe('memory message provenance', () => { beforeEach(() => { vi.clearAllMocks() resetDbChainMock() - mocks.isEnforced.mockReturnValue(enforced) mocks.decrypt.mockResolvedValue({ decrypted: SECRET }) mocks.loadWorkspace.mockResolvedValue({ workspaceId: SCOPE.workspaceId, @@ -283,7 +276,6 @@ describe.each([false, true])('memory message provenance with enforcement %s', (e const context = executionContext() expect((await new Memory().fetchMemoryMessages(context, INPUTS))[0].content).toBe('{{TOKEN}}') expect(context.resolvedSecretTraceRegistry?.isComplete()).toBe(true) - expect(mocks.report).not.toHaveBeenCalled() expect(mocks.logger.error).toHaveBeenCalledExactlyOnceWith( 'Validated historical memory secret provenance', { @@ -400,8 +392,7 @@ describe.each([false, true])('memory message provenance with enforcement %s', (e await importDurableSecretProvenance( readerRegistry, selector.select(messages, false), - messages, - 'memory' + messages ) ).toBe(true) expect(readerRegistry.exportProvenance().entries).toHaveLength(1) @@ -454,7 +445,6 @@ describe.each([false, true])('memory message provenance with enforcement %s', (e expect(result[0].content).toBe('{{TOKEN}}') expect(execution.resolvedSecretTraceRegistry?.isComplete()).toBe(true) expect(mocks.decrypt).toHaveBeenCalledWith('corrupt-ciphertext', { logFailure: false }) - expect(mocks.report).not.toHaveBeenCalled() expect(mocks.logger.error).toHaveBeenCalledWith( 'Historical memory secret provenance could not be recovered', { diff --git a/apps/sim/lib/memory/secret-provenance.ts b/apps/sim/lib/memory/secret-provenance.ts index cc6fd560cf8..22d200ca4bf 100644 --- a/apps/sim/lib/memory/secret-provenance.ts +++ b/apps/sim/lib/memory/secret-provenance.ts @@ -120,7 +120,7 @@ export async function bindMemorySecretProvenanceToMessages( if (provenance.status === 'unknown' || provenance.entries.length === 0) return provenance const registry = new ResolvedSecretTraceRegistry() - if (!(await importDurableSecretProvenance(registry, provenance, messages, 'memory'))) { + if (!(await importDurableSecretProvenance(registry, provenance, messages))) { return { status: 'unknown' } } const entries = new Map() diff --git a/apps/sim/lib/table/rows/secret-provenance.postgres.test.ts b/apps/sim/lib/table/rows/secret-provenance.postgres.test.ts index d0a43d6558e..2fda878794d 100644 --- a/apps/sim/lib/table/rows/secret-provenance.postgres.test.ts +++ b/apps/sim/lib/table/rows/secret-provenance.postgres.test.ts @@ -6,7 +6,7 @@ * From apps/sim, run: * `TABLE_PROVENANCE_TEST_DATABASE_URL=postgresql://user@127.0.0.1:5432/postgres bun run test lib/table/rows/secret-provenance.postgres.test.ts` * CI needs a local PostgreSQL service and this variable; the default unit suite separately - * checks flag policy, stale-snapshot reporting, and write-event attribution without a database. + * checks enforcement, stale-snapshot reporting, and write-event attribution without a database. */ import { userTableRows } from '@sim/db/schema' import { loggingSessionMock } from '@sim/testing' @@ -39,15 +39,11 @@ import { cancelWorkflowGroupRuns } from '@/lib/table/workflow-columns' import { executeWorkflow } from '@/lib/workflows/executor/execute-workflow' import type { ExecutionCallbacks } from '@/executor/execution/types' -const { database, mockIsEnforced, mockReport, mockError, mockExecuteWorkflowCore } = vi.hoisted( - () => ({ - database: { current: undefined as PostgresJsDatabase | undefined }, - mockIsEnforced: vi.fn(() => false), - mockReport: vi.fn(), - mockError: vi.fn(), - mockExecuteWorkflowCore: vi.fn(), - }) -) +const { database, mockError, mockExecuteWorkflowCore } = vi.hoisted(() => ({ + database: { current: undefined as PostgresJsDatabase | undefined }, + mockError: vi.fn(), + mockExecuteWorkflowCore: vi.fn(), +})) vi.unmock('@sim/db/schema') vi.unmock('drizzle-orm') @@ -88,10 +84,6 @@ vi.mock('@/lib/workflows/executor/execution-core', () => ({ vi.mock('@/lib/workflows/executor/pause-persistence', () => ({ handlePostExecutionPauseState: vi.fn(), })) -vi.mock('@/lib/execution/durable-secret-provenance-enforcement', () => ({ - isDurableSecretProvenanceEnforced: mockIsEnforced, - reportUnrecordedDurableProvenance: mockReport, -})) const databaseUrl = process.env.TABLE_PROVENANCE_TEST_DATABASE_URL if (databaseUrl && !['localhost', '127.0.0.1', '[::1]'].includes(new URL(databaseUrl).hostname)) { @@ -238,7 +230,6 @@ describe.skipIf(!databaseUrl)('table provenance in PostgreSQL', () => { beforeEach(async () => { vi.clearAllMocks() - mockIsEnforced.mockReturnValue(false) if (!connection) throw new Error('PostgreSQL test database is not initialized') await connection.unsafe( 'TRUNCATE table_row_executions, user_table_rows, user_table_row_secret_provenance, user_table_definitions' @@ -280,7 +271,6 @@ describe.skipIf(!databaseUrl)('table provenance in PostgreSQL', () => { } it('keeps the original single-row value and secret lineage across a concurrent stamped write', async () => { - mockIsEnforced.mockReturnValue(true) await insertRow({ status: 'exact', entries: [ @@ -304,7 +294,6 @@ describe.skipIf(!databaseUrl)('table provenance in PostgreSQL', () => { }) it('captures only projected, returned rows and ignores an unknown pagination witness', async () => { - mockIsEnforced.mockReturnValue(true) await insertRow({ status: 'exact', entries: [secretEntry] }) await insertRow({ id: 'row-2', status: 'unknown' }) const reader = new TableRowProvenanceReader(scope) @@ -327,7 +316,6 @@ describe.skipIf(!databaseUrl)('table provenance in PostgreSQL', () => { }) it('captures insert provenance before a later writer changes the returned row', async () => { - mockIsEnforced.mockReturnValue(true) const reader = new TableRowProvenanceReader(scope) const row = await insertOrderedRow({ tableId: 'table-1', @@ -346,7 +334,6 @@ describe.skipIf(!databaseUrl)('table provenance in PostgreSQL', () => { }) it('returns the actual merged row and captures its lineage before the write transaction commits', async () => { - mockIsEnforced.mockReturnValue(true) await insertRow({ status: 'exact' }) if (!database.current) throw new Error('Database unavailable') const transact = database.current.transaction.bind(database.current) @@ -490,7 +477,6 @@ describe.skipIf(!databaseUrl)('table provenance in PostgreSQL', () => { ) expect(stored.entries).toEqual(baseStatus === 'exact' ? [boundEntry] : []) expect(fireTableTrigger).toHaveBeenCalledOnce() - mockIsEnforced.mockReturnValue(true) const provenance = await loadTableRowSecretProvenance( [{ id: 'row-1', updatedAt: written!.updatedAt }], scope @@ -724,43 +710,38 @@ describe.skipIf(!databaseUrl)('table provenance in PostgreSQL', () => { unrecorded: false, }, { name: 'exact-empty', fixture: { status: 'exact' }, unrecorded: false }, - ])('classifies $name explicitly under both flag settings', async ({ fixture, unrecorded }) => { - await insertRow(fixture) - for (const enforced of [false, true]) { - mockIsEnforced.mockReturnValue(enforced) - mockReport.mockClear() + ])( + 'classifies $name explicitly without a compatibility bypass', + async ({ fixture, unrecorded }) => { + await insertRow(fixture) + await expect( getTableSnapshotModelMountSafety({ tableId: 'table-1', workspaceId: 'workspace-1', rowsVersion: 7, }) - ).resolves.toBe(enforced && unrecorded ? 'unsafe-provenance' : 'safe') - expect(mockReport).toHaveBeenCalledTimes(unrecorded && !enforced ? 1 : 0) + ).resolves.toBe(unrecorded ? 'unsafe-provenance' : 'safe') } - }) + ) it.each([ { name: 'known secret entries', entries: [secretEntry] }, { name: 'malformed array', entries: [null] }, { name: 'malformed object', entries: {} }, - ])( - 'keeps $name unsafe with the flag off and does not report a proceeded read', - async ({ entries }) => { - await insertRow({ status: 'exact', entries }) - await insertRow({ id: 'unrecorded-row', status: 'unknown' }) - await expect( - getTableSnapshotModelMountSafety({ - tableId: 'table-1', - workspaceId: 'workspace-1', - rowsVersion: 7, - }) - ).resolves.toBe('unsafe-provenance') - expect(mockReport).not.toHaveBeenCalled() - } - ) + ])('keeps $name unsafe unsafe without a compatibility bypass', async ({ entries }) => { + await insertRow({ status: 'exact', entries }) + await insertRow({ id: 'unrecorded-row', status: 'unknown' }) + await expect( + getTableSnapshotModelMountSafety({ + tableId: 'table-1', + workspaceId: 'workspace-1', + rowsVersion: 7, + }) + ).resolves.toBe('unsafe-provenance') + }) - it('returns one count for a stable allowed snapshot containing several unrecorded rows', async () => { + it('refuses a stable snapshot containing several unrecorded rows', async () => { await insertRow({ id: 'missing' }) await insertRow({ id: 'unknown', status: 'unknown' }) await expect( @@ -769,13 +750,7 @@ describe.skipIf(!databaseUrl)('table provenance in PostgreSQL', () => { workspaceId: 'workspace-1', rowsVersion: 7, }) - ).resolves.toBe('safe') - expect(mockReport).toHaveBeenCalledExactlyOnceWith({ - surface: 'table-row', - cause: 'row-sidecar-not-exact', - affectedCount: 2, - workspaceId: 'workspace-1', - }) + ).resolves.toBe('unsafe-provenance') }) it('preserves legacy compatibility and records every SQL-derived unknown by cause', async () => { @@ -909,7 +884,6 @@ describe.skipIf(!databaseUrl)('table provenance in PostgreSQL', () => { ).resolves.toEqual({ version: 1, complete: true, entries: expectedEntries, scope }) } expect(mockError).not.toHaveBeenCalled() - expect(mockReport).not.toHaveBeenCalled() }) it('preserves wide bindings through derived SQL and removes only the deleted column', async () => { diff --git a/apps/sim/lib/table/rows/secret-provenance.test.ts b/apps/sim/lib/table/rows/secret-provenance.test.ts index 36dc1b8f1dd..147f73c97c8 100644 --- a/apps/sim/lib/table/rows/secret-provenance.test.ts +++ b/apps/sim/lib/table/rows/secret-provenance.test.ts @@ -6,9 +6,7 @@ import { dbChainMock, dbChainMockFns, queueTableRows, resetDbChainMock } from '@ import { eq } from 'drizzle-orm' import { beforeEach, describe, expect, it, vi } from 'vitest' -const { mockIsEnforced, mockReport, mockError } = vi.hoisted(() => ({ - mockIsEnforced: vi.fn(() => false), - mockReport: vi.fn(), +const { mockError } = vi.hoisted(() => ({ mockError: vi.fn(), })) @@ -16,12 +14,6 @@ vi.mock('@sim/logger', () => ({ createLogger: () => ({ error: mockError, warn: vi.fn() }), })) -vi.mock('@/lib/execution/durable-secret-provenance-enforcement', () => ({ - DURABLE_SECRET_PROVENANCE_SURFACES: ['memory', 'table-row', 'knowledge'], - isDurableSecretProvenanceEnforced: mockIsEnforced, - reportUnrecordedDurableProvenance: mockReport, -})) - import { PROVENANCE_MAX_SERIALIZED_BYTES } from '@/lib/execution/provenance-limits' import type { DbTransaction } from '@/lib/table/planner' import { @@ -65,7 +57,6 @@ describe('table row secret provenance', () => { beforeEach(() => { vi.clearAllMocks() resetDbChainMock() - mockIsEnforced.mockReturnValue(false) }) it('checks a version-pinned table with one aggregate rather than loading its rows', async () => { @@ -99,7 +90,6 @@ describe('table row secret provenance', () => { ).resolves.toBe('unsafe-provenance') expect(dbChainMockFns.limit).toHaveBeenCalledTimes(2) - expect(mockReport).not.toHaveBeenCalled() }) it('rejects a snapshot when the table changes during the safety check', async () => { @@ -114,35 +104,21 @@ describe('table row secret provenance', () => { rowsVersion: 7, }) ).resolves.toBe('stale') - expect(mockReport).not.toHaveBeenCalled() }) - it.each([false, true])( - 'applies the table-row enforcement policy to snapshot absences (%s)', - async (enforced) => { - mockIsEnforced.mockReturnValue(enforced) - queueTableRows(userTableDefinitions, [{ rowsVersion: 7 }]) - queueTableRows(userTableDefinitions, [{ rowsVersion: 7 }]) - queueTableRows(userTableRows, [{ unsafeCount: '0', unrecordedCount: '3' }]) - - await expect( - getTableSnapshotModelMountSafety({ - tableId: 'table-1', - workspaceId: 'workspace-1', - rowsVersion: 7, - }) - ).resolves.toBe(enforced ? 'unsafe-provenance' : 'safe') + it('refuses snapshot provenance absences', async () => { + queueTableRows(userTableDefinitions, [{ rowsVersion: 7 }]) + queueTableRows(userTableDefinitions, [{ rowsVersion: 7 }]) + queueTableRows(userTableRows, [{ unsafeCount: '0', unrecordedCount: '3' }]) - if (enforced) expect(mockReport).not.toHaveBeenCalled() - else - expect(mockReport).toHaveBeenCalledExactlyOnceWith({ - surface: 'table-row', - cause: 'row-sidecar-not-exact', - affectedCount: 3, - workspaceId: 'workspace-1', - }) - } - ) + await expect( + getTableSnapshotModelMountSafety({ + tableId: 'table-1', + workspaceId: 'workspace-1', + rowsVersion: 7, + }) + ).resolves.toBe('unsafe-provenance') + }) it('keeps untouched legacy rows readable with exact-empty provenance', async () => { queueTableRows(userTableRows, [ @@ -371,8 +347,7 @@ describe('table row secret provenance', () => { }) }) - it('fails closed for stale tracked rows once the table-row surface is enforced', async () => { - mockIsEnforced.mockReturnValue(true) + it('refuses stale tracked rows', async () => { queueTableRows(userTableRows, [ { id: 'tracked-row', @@ -398,11 +373,7 @@ describe('table row secret provenance', () => { }) }) - /** - * The shape that broke production: one unrecorded row in a page voided the whole read, and a - * page is what a `query_rows` block hands downstream, so every later model boundary refused. - */ - it('keeps a page readable when one row is unrecorded, without dropping its siblings', async () => { + it('refuses a page containing an unknown tracked row', async () => { queueTableRows(userTableRows, [ { id: 'unknown-row', @@ -442,17 +413,10 @@ describe('table row secret provenance', () => { ) ).resolves.toEqual({ version: 1, - complete: true, - entries: [{ encryptedValue: 'encrypted-local', name: 'LOCAL_SECRET' }], + complete: false, + entries: [], scope: { userId: 'user-1', workspaceId: 'workspace-1' }, }) - expect(mockReport).toHaveBeenCalledWith({ - surface: 'table-row', - cause: 'row-sidecar-not-exact', - affectedCount: 1, - workspaceId: 'workspace-1', - actorUserId: 'user-1', - }) }) it('rejects contradictory duplicate row crossings before reading provenance', async () => { diff --git a/apps/sim/lib/table/rows/secret-provenance.ts b/apps/sim/lib/table/rows/secret-provenance.ts index dbd50ee65bc..7e4a952e304 100644 --- a/apps/sim/lib/table/rows/secret-provenance.ts +++ b/apps/sim/lib/table/rows/secret-provenance.ts @@ -7,10 +7,6 @@ import { } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { and, asc, eq, gt, inArray, type SQL, sql } from 'drizzle-orm' -import { - isDurableSecretProvenanceEnforced, - reportUnrecordedDurableProvenance, -} from '@/lib/execution/durable-secret-provenance-enforcement' import { SecretProvenanceBudget } from '@/lib/execution/provenance-budget' import { PROVENANCE_MAX_ENTRIES, @@ -833,16 +829,7 @@ export async function getTableSnapshotModelMountSafety(options: { } if (!counts || Number(counts.unsafeCount) > 0) return 'unsafe-provenance' - const unrecordedCount = Number(counts.unrecordedCount) - if (unrecordedCount > 0) { - if (isDurableSecretProvenanceEnforced('table-row')) return 'unsafe-provenance' - reportUnrecordedDurableProvenance({ - surface: 'table-row', - cause: 'row-sidecar-not-exact', - affectedCount: unrecordedCount, - workspaceId: options.workspaceId, - }) - } + if (Number(counts.unrecordedCount) > 0) return 'unsafe-provenance' return 'safe' } @@ -939,7 +926,6 @@ export async function loadTableRowSecretProvenance( const currentById = new Map(currentRows.map((row) => [row.id, row])) const aggregator = createStoredEntryAggregator(scope) - let unrecordedRowCount = 0 for (const rowId of rowIds) { const current = currentById.get(rowId) const crossing = crossingById.get(rowId) @@ -953,16 +939,7 @@ export async function loadTableRowSecretProvenance( current.sidecarStatus !== 'exact' || !current.sidecarIsCurrent ) { - /** - * One such row would otherwise void the whole page, and a page is what a `query_rows` block - * hands downstream — so a single row nobody recorded provenance for latched every run that - * read the table. Unenforced, the row contributes nothing, exactly like the legacy row above. - */ - if (isDurableSecretProvenanceEnforced('table-row')) { - return incomplete('row-sidecar-not-exact') - } - unrecordedRowCount += 1 - continue + return incomplete('row-sidecar-not-exact') } const parsed = normalizeStoredEntries(current.sidecarEntries) if (!parsed) return incomplete('row-sidecar-malformed') @@ -972,16 +949,6 @@ export async function loadTableRowSecretProvenance( } } - if (unrecordedRowCount > 0) { - reportUnrecordedDurableProvenance({ - surface: 'table-row', - cause: 'row-sidecar-not-exact', - affectedCount: unrecordedRowCount, - ...(scope.workspaceId ? { workspaceId: scope.workspaceId } : {}), - actorUserId: scope.userId, - }) - } - const entries = aggregator.build() if (!entries) return incomplete('row-provenance-budget-exceeded') const provenance: ResolvedSecretTraceProvenanceV1 = { diff --git a/apps/sim/lib/uploads/contexts/workspace/workspace-file-secret-provenance.test.ts b/apps/sim/lib/uploads/contexts/workspace/workspace-file-secret-provenance.test.ts index b6bdbd17523..f3459324a8a 100644 --- a/apps/sim/lib/uploads/contexts/workspace/workspace-file-secret-provenance.test.ts +++ b/apps/sim/lib/uploads/contexts/workspace/workspace-file-secret-provenance.test.ts @@ -5,17 +5,12 @@ import { workspaceFileSecretProvenance, workspaceFiles } from '@sim/db/schema' import { dbChainMock, dbChainMockFns, queueTableRows, resetDbChainMock } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' -const { mockIsEnforced, mockReport, mockReportWrite, mockReportRefusal } = vi.hoisted(() => ({ - mockIsEnforced: vi.fn(() => false), - mockReport: vi.fn(), +const { mockReportWrite, mockReportRefusal } = vi.hoisted(() => ({ mockReportWrite: vi.fn(), mockReportRefusal: vi.fn(), })) -vi.mock('@/lib/execution/durable-secret-provenance-enforcement', () => ({ - DURABLE_SECRET_PROVENANCE_SURFACES: ['memory', 'table-row', 'knowledge', 'workspace-file'], - isDurableSecretProvenanceEnforced: mockIsEnforced, - reportUnrecordedDurableProvenance: mockReport, +vi.mock('@/lib/execution/durable-secret-provenance-telemetry', () => ({ reportDurableSecretProvenanceWrite: mockReportWrite, reportDurableSecretProvenanceRefusal: mockReportRefusal, })) @@ -77,27 +72,25 @@ describe('execution file sidecars at model boundaries', () => { status, entries, } - for (const enforced of [false, true]) { - mockIsEnforced.mockReturnValue(enforced) - queueTableRows(workspaceFiles, [row]) - expect(await isModelSafeWorkspaceFileKey(key, { workspaceId: 'workspace-1' })).toBe(safe) - queueTableRows(workspaceFiles, [row]) - expect( - await filterModelSafeWorkspaceFileAttachments([{ id: 'invented-id', key }], { - workspaceId: 'workspace-1', - }) - ).toEqual(safe ? [{ id: 'invented-id', key }] : []) - queueTableRows(workspaceFiles, [row]) - const bound = await getBoundWorkspaceFileSecretProvenance('workspace-1', { - fileId: 'canonical-id', - key, - context: 'execution', - contentUpdatedAt: CONTENT_UPDATED_AT, + + queueTableRows(workspaceFiles, [row]) + expect(await isModelSafeWorkspaceFileKey(key, { workspaceId: 'workspace-1' })).toBe(safe) + queueTableRows(workspaceFiles, [row]) + expect( + await filterModelSafeWorkspaceFileAttachments([{ id: 'invented-id', key }], { + workspaceId: 'workspace-1', }) - expect(bound.status).toBe( - version === null || (status === 'exact' && !stale) ? 'exact' : 'unknown' - ) - } + ).toEqual(safe ? [{ id: 'invented-id', key }] : []) + queueTableRows(workspaceFiles, [row]) + const bound = await getBoundWorkspaceFileSecretProvenance('workspace-1', { + fileId: 'canonical-id', + key, + context: 'execution', + contentUpdatedAt: CONTENT_UPDATED_AT, + }) + expect(bound.status).toBe( + version === null || (status === 'exact' && !stale) ? 'exact' : 'unknown' + ) } ) }) @@ -106,7 +99,6 @@ describe('workspace file secret provenance', () => { beforeEach(() => { vi.clearAllMocks() resetDbChainMock() - mockIsEnforced.mockReturnValue(false) dbChainMockFns.returning.mockResolvedValue([{ id: 'tracked-file' }]) }) @@ -501,7 +493,6 @@ describe('workspace file secret provenance', () => { * stored `unknown` above is dropped: a writer refused those bytes on purpose, which is a * different claim from nobody having recorded them, and no policy relaxes it. */ - { id: 'unrecorded-id', key: 'unrecorded-key' }, { id: 'pre-marker-sidecar-id', key: 'pre-marker-sidecar-key' }, { id: 'legacy-id', key: 'legacy-key' }, { id: 'inline-file' }, @@ -848,7 +839,7 @@ describe('workspace file secret provenance', () => { * Unrecorded says exactly what an untracked file says, and that one has always mounted. There is * nothing to import either way, so the mount proceeds and the workspace is told. */ - it('mounts an unrecorded file without importing provenance for it', async () => { + it('refuses to mount an unrecorded tracked file', async () => { const registry = { importProvenance: vi.fn(), isPermanentlyIncomplete: vi.fn().mockReturnValue(false), @@ -869,9 +860,8 @@ describe('workspace file secret provenance', () => { identity: { fileId: 'file-1', key: 'file-key', context: 'workspace' }, registry, }) - ).resolves.toBe(true) + ).resolves.toBe(false) expect(registry.importProvenance).not.toHaveBeenCalled() - expect(mockReport).toHaveBeenCalledWith(expect.objectContaining({ surface: 'workspace-file' })) }) it('rejects tracked mounted-file provenance when no complete runtime registry can import it', async () => { @@ -1608,79 +1598,7 @@ describe('workspace file secret provenance', () => { ) }) - /** - * The whole reason this surface was brought under the policy: a file that was never tracked - * already reads as safe two branches earlier, and an unrecorded one says exactly the same thing - * about its contents. Refusing only the second left files permanently unreadable for having - * tried to record provenance and failed. - */ - it('reads an unrecorded file, as it already reads an untracked one', async () => { - queueTableRows(workspaceFiles, [ - { - id: 'unrecorded-id', - key: 'unrecorded-key', - workspaceId: 'workspace-1', - context: 'workspace', - fileContentUpdatedAt: CONTENT_UPDATED_AT, - secretProvenanceVersion: 1, - provenanceContentUpdatedAt: CONTENT_UPDATED_AT, - status: 'unrecorded', - entries: [], - }, - ]) - - await expect(isModelSafeWorkspaceFileKey('unrecorded-key')).resolves.toBe(true) - expect(mockReport).toHaveBeenCalledWith( - expect.objectContaining({ surface: 'workspace-file', cause: 'durable-provenance-unknown' }) - ) - }) - - /** The audit row names who read past the absence when the caller can say; null otherwise. */ - it('carries the actor into the unrecorded-read report when the caller supplies one', async () => { - queueTableRows(workspaceFiles, [ - { - id: 'unrecorded-id', - key: 'unrecorded-key', - workspaceId: 'workspace-1', - context: 'workspace', - fileContentUpdatedAt: CONTENT_UPDATED_AT, - secretProvenanceVersion: 1, - provenanceContentUpdatedAt: CONTENT_UPDATED_AT, - status: 'unrecorded', - entries: [], - }, - ]) - - await expect( - isModelSafeWorkspaceFileKey('unrecorded-key', { actorUserId: 'user-1' }) - ).resolves.toBe(true) - expect(mockReport).toHaveBeenCalledWith(expect.objectContaining({ actorUserId: 'user-1' })) - - mockReport.mockClear() - queueTableRows(workspaceFiles, [ - { - id: 'unrecorded-id', - key: 'unrecorded-key', - workspaceId: 'workspace-1', - context: 'workspace', - fileContentUpdatedAt: CONTENT_UPDATED_AT, - secretProvenanceVersion: 1, - provenanceContentUpdatedAt: CONTENT_UPDATED_AT, - status: 'unrecorded', - entries: [], - }, - ]) - await expect(isModelSafeWorkspaceFileKey('unrecorded-key')).resolves.toBe(true) - expect(mockReport).toHaveBeenCalledWith(expect.objectContaining({ actorUserId: null })) - }) - - /** - * The row has to be a recorded absence, not a refusal. A stored `unknown` is refused whatever the - * flag says, so asserting against one would pass with enforcement off and prove nothing about the - * switch this whole posture rests on. - */ - it('refuses an unrecorded file again once the surface is closed', async () => { - mockIsEnforced.mockReturnValue(true) + it('refuses an unrecorded tracked file', async () => { queueTableRows(workspaceFiles, [ { id: 'unrecorded-id', @@ -1696,7 +1614,6 @@ describe('workspace file secret provenance', () => { ]) await expect(isModelSafeWorkspaceFileKey('unrecorded-key')).resolves.toBe(false) - expect(mockReport).not.toHaveBeenCalled() expect(mockReportRefusal).toHaveBeenCalledWith({ surface: 'workspace-file', cause: 'workspace-file-unrecorded-enforced', @@ -1769,7 +1686,7 @@ describe('createWorkspaceFileSecretProvenanceFromRegistry write decision', () => /** * A registry latched with nothing resolved is an absence, not a taint: no plaintext exists in - * the context to be in the bytes, so the file must stay readable under the unrecorded policy. + * the context to be in the bytes, so the writer records an absence instead of known taint. * Stamping taint here made one failed workflow run hard-refuse every file its chat later wrote. */ it('classifies a latched registry holding no active entries as unrecorded', async () => { diff --git a/apps/sim/lib/uploads/contexts/workspace/workspace-file-secret-provenance.ts b/apps/sim/lib/uploads/contexts/workspace/workspace-file-secret-provenance.ts index d3afdaef602..2ca4e4299f4 100644 --- a/apps/sim/lib/uploads/contexts/workspace/workspace-file-secret-provenance.ts +++ b/apps/sim/lib/uploads/contexts/workspace/workspace-file-secret-provenance.ts @@ -13,11 +13,9 @@ import { isPrivateSecretProvenanceScopeCompatible, } from '@/lib/execution/durable-secret-provenance' import { - isDurableSecretProvenanceEnforced, reportDurableSecretProvenanceRefusal, reportDurableSecretProvenanceWrite, - reportUnrecordedDurableProvenance, -} from '@/lib/execution/durable-secret-provenance-enforcement' +} from '@/lib/execution/durable-secret-provenance-telemetry' import { PROVENANCE_MAX_ENTRIES, PROVENANCE_MAX_SERIALIZED_BYTES, @@ -37,16 +35,9 @@ export const MODEL_UNSAFE_WORKSPACE_FILE_ERROR_MESSAGE = 'File cannot be sent to a model because its secret provenance is unavailable' /** - * What can be said about the secrets a file's bytes carry. - * - * The sidecar's `status` column stores all three values, and reader and storage still mean - * different things by two of them. Stored `'unrecorded'` is the writer saying nobody vouched for - * these bytes; stored `'unknown'` is a writer that knew secrets were in scope and could not map - * them. This union records what a *reader* can conclude, so a missing row, moved version, or stale - * or malformed sidecar also lands on `'unknown'` — "the writer refused" and "there is nothing - * usable to read" share a conclusion but not a stored value. Only `'unrecorded'` is an absence a - * policy may relax; it is the name the shared vocabulary already uses - * (`reportUnrecordedDurableProvenance`, the `secret_provenance.unrecorded` audit action). + * Exact sidecars identify known secret contributions. Unrecorded sidecars represent missing + * writer provenance; unknown sidecars represent taint or an invalid binding. Both refuse reads + * for tracked files, while null tracking markers retain legacy compatibility. */ export type WorkspaceFileSecretProvenance = | { status: 'exact'; entries: readonly WorkspaceFileSecretProvenanceEntry[] } @@ -272,11 +263,7 @@ export async function createWorkspaceFileSecretProvenanceFromRegistry( representations: readonly WorkspaceFileSecretProvenanceRepresentation[] = [], representationsComplete = true ): Promise { - /** - * No registry means no recorder ran, so nothing was written down about these bytes. That is an - * absence, and it is the one thing this surface's policy may relax — distinct from the taint - * every `safe: false` below produces, which a caller persists as `unknown` and no policy relaxes. - */ + /** Missing recording context is persisted separately from known taint; both refuse later reads. */ if (!registry) return { safe: true, provenance: { status: 'unrecorded' } } const sourceProvenance = registry.exportCommittedProvenanceForValue(sourceValue) const persistedProvenance = Object.is(sourceValue, persistedValue) @@ -791,10 +778,8 @@ export async function getBoundWorkspaceFileSecretProvenance( row.provenanceContentUpdatedAt?.getTime() === row.fileContentUpdatedAt.getTime() if (!bindingIsCurrent || !isValidStoredEntries(row.entries)) return { status: 'unknown' } /** - * The one shape the surface's policy may relax: a sidecar bound to this exact content recording - * that nobody vouched for it — the same statement the untracked file above makes. A stored - * `unknown` is the opposite claim, written by a writer that refused these bytes on purpose, and - * stays refused. + * Preserve the writer's absence/taint distinction for diagnostics. Readers refuse both; + * legacy compatibility is determined only by the tracking marker above. */ if (row.status === 'unrecorded') return { status: 'unrecorded' } if (row.status !== 'exact') return { status: 'unknown' } @@ -851,12 +836,6 @@ export async function getBoundWorkspaceFileSecretProvenanceByMetadata( result.set(row.id, { status: 'unknown' }) continue } - /** - * Same answer the single-file reader gives the same row. Collapsing a recorded absence into - * `unknown` here would leave two classifiers describing one policy differently, and the - * caller that eventually distinguishes them would get a different verdict depending on which - * one it happened to call. - */ if (row.status === 'unrecorded') { result.set(row.id, { status: 'unrecorded' }) continue @@ -877,43 +856,14 @@ export async function getBoundWorkspaceFileSecretProvenanceByMetadata( return result } -/** - * Whether a file nobody could vouch for may still be read. - * - * A file that was never tracked already returns exact-empty a branch earlier, and it makes exactly - * the same statement as this one: nobody recorded which secrets these bytes carry. Refusing only - * the second left a writer that momentarily could not vouch permanently worse off than one that - * never tried, with no way back — nothing rewrites a file's provenance but another content write, - * so the file simply stopped working, everywhere, for good. - * - * So it reads, and the workspace is told. Deliberately narrower than "not exact": a stale binding - * describes content that has since changed and a malformed sidecar is a fault, and neither is the - * absence this covers. Closing the surface again is a matter of naming it in - * `DURABLE_SECRET_PROVENANCE_ENFORCED_SURFACES`. - */ -export function mayReadUnrecordedWorkspaceFile( - workspaceId: string | undefined, - count = 1, - actorUserId?: string -): boolean { - if (isDurableSecretProvenanceEnforced('workspace-file')) { - reportDurableSecretProvenanceRefusal({ - surface: 'workspace-file', - cause: 'workspace-file-unrecorded-enforced', - workspaceId, - }) - return false - } - if (count > 0) { - reportUnrecordedDurableProvenance({ - surface: 'workspace-file', - cause: 'durable-provenance-unknown', - ...(count > 1 ? { affectedCount: count } : {}), - ...(workspaceId ? { workspaceId } : {}), - actorUserId: actorUserId ?? null, - }) - } - return true +/** Refuses tracked content whose writer could not record its provenance. */ +function refuseUnrecordedWorkspaceFile(workspaceId: string | undefined): false { + reportDurableSecretProvenanceRefusal({ + surface: 'workspace-file', + cause: 'workspace-file-unrecorded-enforced', + workspaceId, + }) + return false } /** Reports the canonical file identity without exposing its storage key or contents. */ @@ -946,8 +896,6 @@ export async function importWorkspaceFileSecretProvenanceForModelView(args: { registry?: ResolvedSecretTraceRegistry view: 'complete' | 'derived' | 'opaque' value?: unknown - /** Whose access authorized the read, for the unrecorded-read audit entry; null when unnameable. */ - actorUserId?: string }): Promise { const provenance = await getBoundWorkspaceFileSecretProvenance(args.workspaceId, args.identity) if (provenance.status === 'unknown') { @@ -958,7 +906,7 @@ export async function importWorkspaceFileSecretProvenanceForModelView(args: { ) } if (provenance.status === 'unrecorded') { - return mayReadUnrecordedWorkspaceFile(args.workspaceId, 1, args.actorUserId) + return refuseUnrecordedWorkspaceFile(args.workspaceId) } if (provenance.entries.length === 0) return true if (args.view === 'opaque' || !args.registry) { @@ -999,7 +947,7 @@ export async function isOpaqueWorkspaceFileEgressSafe( identity.fileId ) } - if (provenance.status === 'unrecorded') return mayReadUnrecordedWorkspaceFile(workspaceId) + if (provenance.status === 'unrecorded') return refuseUnrecordedWorkspaceFile(workspaceId) return ( provenance.entries.length === 0 || refuseWorkspaceFileProvenance( @@ -1019,8 +967,6 @@ export async function importWorkspaceFileSecretProvenanceForRuntime(args: { workspaceId: string identity: WorkspaceFileSecretProvenanceIdentity registry?: ResolvedSecretTraceRegistry - /** Whose access authorized the read, for the unrecorded-read audit entry; null when unnameable. */ - actorUserId?: string }): Promise { const provenance = await getBoundWorkspaceFileSecretProvenance(args.workspaceId, args.identity) if (provenance.status === 'unknown') { @@ -1031,7 +977,7 @@ export async function importWorkspaceFileSecretProvenanceForRuntime(args: { ) } if (provenance.status === 'unrecorded') { - return mayReadUnrecordedWorkspaceFile(args.workspaceId, 1, args.actorUserId) + return refuseUnrecordedWorkspaceFile(args.workspaceId) } if (provenance.entries.length === 0) return true if (!args.registry) { @@ -1059,7 +1005,7 @@ export async function filterModelSafeWorkspaceFileAttachments< TAttachment extends WorkspaceFileAttachmentIdentity, >( attachments: readonly TAttachment[], - options: { workspaceId?: string; actorUserId?: string } = {} + options: { workspaceId?: string } = {} ): Promise { if (attachments.length === 0) return [] if (attachments.length > PROVENANCE_MAX_ENTRIES) { @@ -1098,24 +1044,19 @@ export async function filterModelSafeWorkspaceFileAttachments< return false } unrecorded += 1 - return !isDurableSecretProvenanceEnforced('workspace-file') + return false }) if (refused > 0) { refuseWorkspaceFileProvenance('workspace-file-provenance-unavailable', options.workspaceId) } /** One report for the whole set of attachments, which is one read, rather than one per file. */ if (unrecorded > 0) { - mayReadUnrecordedWorkspaceFile(options.workspaceId, unrecorded, options.actorUserId) + refuseUnrecordedWorkspaceFile(options.workspaceId) } return kept } -/** - * Classifies one row without deciding it. `unrecorded` is kept apart from `unsafe` because only the - * first is the same statement an untracked file makes, and only the first is the surface's policy - * to relax — a stale binding describes content that has since changed, and a malformed sidecar is a - * fault no policy relaxes. - */ +/** Keeps missing writer provenance distinct from taint for refusal diagnostics. */ type ModelSafeWorkspaceFileClassification = 'safe' | 'unrecorded' | 'unsafe' function classifyModelSafeWorkspaceFileRow( @@ -1174,7 +1115,7 @@ async function loadModelSafeWorkspaceFileRows( */ export async function isModelSafeWorkspaceFileKey( key: string, - options: { workspaceId?: string; actorUserId?: string } = {} + options: { workspaceId?: string } = {} ): Promise { return areModelSafeWorkspaceFileKeys([key], options) } @@ -1186,7 +1127,7 @@ export async function isModelSafeWorkspaceFileKey( */ export async function areModelSafeWorkspaceFileKeys( keys: readonly string[], - options: { workspaceId?: string; actorUserId?: string } = {} + options: { workspaceId?: string } = {} ): Promise { const uniqueKeys = [...new Set(keys.filter((key) => key.length > 0))] if (uniqueKeys.length === 0) return true @@ -1215,8 +1156,5 @@ export async function areModelSafeWorkspaceFileKeys( if (classification === 'unrecorded') unrecorded += 1 } /** One report for the batch, not one per key: a caller checking many keys is one read. */ - return ( - unrecorded === 0 || - mayReadUnrecordedWorkspaceFile(options.workspaceId, unrecorded, options.actorUserId) - ) + return unrecorded === 0 || refuseUnrecordedWorkspaceFile(options.workspaceId) } From 4e492258bcaae149c2976c36736effc5819105f6 Mon Sep 17 00:00:00 2001 From: Waleed Date: Thu, 17 Sep 2026 12:44:40 -0700 Subject: [PATCH 09/15] fix(search): resume Google crawls around unavailable accounts (#7934) * fix(search): resume Google crawls around unavailable accounts * fix(search): harden crawl completion and recovery * improvement(search): generalize connector partition progress * fix(search): clear unverifiable placeholder permissions --- .github/workflows/test-build.yml | 5 + apps/docs/content/docs/search/gmail.mdx | 2 +- .../content/docs/search/google-calendar.mdx | 6 +- .../docs/content/docs/search/google-drive.mdx | 3 +- .../google-drive/company-crawl.test.ts | 8 + .../connectors/google-drive/company-crawl.ts | 33 +- .../google-drive/google-drive-errors.ts | 14 +- .../google-workspace/api-errors.test.ts | 58 + .../connectors/google-workspace/api-errors.ts | 44 +- .../google-workspace/company-crawl.test.ts | 1 + .../google-workspace/company-crawl.ts | 51 +- .../google-workspace/company-work.ts | 22 + apps/sim/connectors/listing-failures.test.ts | 56 + apps/sim/connectors/listing-failures.ts | 2 + apps/sim/connectors/source-error.ts | 17 +- apps/sim/connectors/types.ts | 8 +- .../connector-lifecycle-locks.integration.ts | 262 + .../connector-partition-work.integration.ts | 539 + .../listing-continuation.integration.ts | 507 +- .../stored-document-recovery.integration.ts | 173 +- .../lib/knowledge/application/connectors.ts | 6 +- .../knowledge/connectors/access-token.test.ts | 38 + .../lib/knowledge/connectors/access-token.ts | 27 +- .../connectors/connector-error.test.ts | 23 +- .../knowledge/connectors/connector-error.ts | 2 + .../google-company-scheduler.test.ts | 534 + .../connectors/google-company-scheduler.ts | 382 + .../connectors/listing-checkpoint.test.ts | 94 + .../connectors/listing-checkpoint.ts | 21 +- .../connectors/member-sync-engine.ts | 2 +- .../knowledge/connectors/partition-store.ts | 197 + .../knowledge/connectors/partition-work.ts | 54 + .../connectors/sync-content-pass.test.ts | 121 +- .../knowledge/connectors/sync-content-pass.ts | 135 +- .../lib/knowledge/connectors/sync-deferral.ts | 2 +- .../knowledge/connectors/sync-engine.test.ts | 2 +- .../lib/knowledge/connectors/sync-engine.ts | 9 +- .../knowledge/connectors/sync-primitives.ts | 2 +- .../documents/ocr-source-validation.test.ts | 62 + .../documents/ocr-source-validation.ts | 14 + .../documents/pdf-ocr-triage.test.ts | 34 + .../documents/processing-recovery.ts | 59 +- apps/sim/lib/oauth/credential-service.test.ts | 75 +- apps/sim/lib/oauth/credential-service.ts | 53 +- .../google-service-account-transport.test.ts | 191 + .../oauth/google-service-account-transport.ts | 79 + .../0356_connector_partition_progress.sql | 28 + .../db/migrations/meta/0356_snapshot.json | 27509 ++++++++++++++++ packages/db/migrations/meta/_journal.json | 7 + packages/db/schema.ts | 61 + packages/testing/src/mocks/schema.mock.ts | 18 + 51 files changed, 31535 insertions(+), 117 deletions(-) create mode 100644 apps/sim/connectors/google-workspace/company-work.ts create mode 100644 apps/sim/connectors/listing-failures.test.ts create mode 100644 apps/sim/lib/knowledge/__integration__/connector-lifecycle-locks.integration.ts create mode 100644 apps/sim/lib/knowledge/__integration__/connector-partition-work.integration.ts create mode 100644 apps/sim/lib/knowledge/connectors/google-company-scheduler.test.ts create mode 100644 apps/sim/lib/knowledge/connectors/google-company-scheduler.ts create mode 100644 apps/sim/lib/knowledge/connectors/partition-store.ts create mode 100644 apps/sim/lib/knowledge/connectors/partition-work.ts create mode 100644 apps/sim/lib/oauth/google-service-account-transport.test.ts create mode 100644 apps/sim/lib/oauth/google-service-account-transport.ts create mode 100644 packages/db/migrations/0356_connector_partition_progress.sql create mode 100644 packages/db/migrations/meta/0356_snapshot.json diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml index 66694c9d63d..af187705e13 100644 --- a/.github/workflows/test-build.yml +++ b/.github/workflows/test-build.yml @@ -235,6 +235,11 @@ jobs: lib/knowledge/__integration__/search-source-pagination.integration.ts lib/knowledge/__integration__/search-reference-batching.integration.ts lib/knowledge/__integration__/embedding-insert-batches.integration.ts + lib/knowledge/__integration__/connector-lifecycle-locks.integration.ts + lib/knowledge/__integration__/connector-deferral.integration.ts + lib/knowledge/__integration__/stored-document-recovery.integration.ts + lib/knowledge/__integration__/connector-partition-work.integration.ts + lib/knowledge/__integration__/listing-continuation.integration.ts lib/knowledge/__integration__/kb-block-search.integration.ts lib/core/outbox/service.integration.ts lib/knowledge/__integration__/connector-upload.integration.ts diff --git a/apps/docs/content/docs/search/gmail.mdx b/apps/docs/content/docs/search/gmail.mdx index ecb646f4e65..b796b3160d7 100644 --- a/apps/docs/content/docs/search/gmail.mdx +++ b/apps/docs/content/docs/search/gmail.mdx @@ -133,7 +133,7 @@ Search schedules syncs hourly. The first sync lists every thread in scope and ca **Member accounts:** later syncs use each mailbox's Gmail change history, unless the configuration has a search filter. A full relisting runs about weekly, or sooner if Gmail no longer retains the saved history. -**Service account:** each sync revisits the selected active mailboxes and resumes unfinished listings. If Google reports that a user's mailbox is not set up or returns a mailbox `failedPrecondition`, Sim records a warning and continues with the remaining users. The crawl stays incomplete and retries affected users on the next scheduled crawl; existing indexed mail is not deleted because a mailbox could not be read. Credential, delegation, and Directory failures still stop the crawl. +**Service account:** each user's progress is saved separately, so a large mailbox or an account-specific access failure does not hold up other users. Temporary failures retry automatically. Unavailable mailboxes are revisited without deleting indexed mail solely because the mailbox could not be read. Sim continues discovering users and refreshing existing mail and permissions while unfinished work resumes. Results with stale or unverified permissions remain hidden. Credential, delegation, Directory, and scope failures still require attention. Updates, removals, and access refresh in the background. Empty mailboxes and filters with no matches complete normally with zero documents. Threads exceeding indexing size limits are skipped and reconsidered when they change. diff --git a/apps/docs/content/docs/search/google-calendar.mdx b/apps/docs/content/docs/search/google-calendar.mdx index 817e7acf770..b16a3111978 100644 --- a/apps/docs/content/docs/search/google-calendar.mdx +++ b/apps/docs/content/docs/search/google-calendar.mdx @@ -62,7 +62,7 @@ Admins can request member connections from **Settings → Sources → People → Open **Settings → Sources → Add source** and select **Google Calendar**. This opens **Connect Google Calendar service account**. To add another connection later, open **Google Calendar** from the Sources list and select **Connect service account**. -This requires a Google Workspace customer and a super administrator to authorize domain-wide delegation. Consumer Gmail accounts cannot use this path. +This requires a Google Workspace customer and a super administrator to authorize domain-wide delegation. Consumer Gmail accounts cannot use this path. In the Google Admin Console, confirm **Apps → Google Workspace → Calendar → Service status** is on for the users you want to index. An active Workspace account does not necessarily have Calendar enabled. @@ -131,7 +131,9 @@ Sim indexes event titles, descriptions, times, locations, and the selected atten Cancelled events, attachment contents, meeting recordings, and transcripts are not indexed. Status entries such as working location, out of office, focus time, and birthdays, and automatically generated reservation events from Gmail are not indexed. Events Google returns only as free/busy blocks, without searchable details, are not indexed. Events outside the selected date window are excluded. Private event details that Google withholds are not available in Search; see [Google's calendar sharing rules](https://developers.google.com/workspace/calendar/api/concepts/sharing). -Search schedules syncs hourly. Event edits, cancellations, access changes, inactive or removed users, and events moving outside the date window are reconciled during completed background syncs. Central crawls page through each selected user and resume unfinished work before removing documents no longer listed. If an individual user's event listing returns a `403` with an explicit `forbidden` reason and no other reasons, Sim records a warning and continues with the remaining users. The crawl stays incomplete and retries affected users on the next scheduled crawl; unread calendars are not treated as empty. A `403` without a reason stops the crawl because its cause is unknown. Credential, delegation, Directory, and other provider failures still stop the crawl. The first sync may take longer, and results appear as indexing progresses; Search is not a live Calendar read. +Search schedules syncs hourly. The first sync may span several background runs; results appear as indexing progresses. Central crawls save each user's progress so a large calendar or an account-specific access failure does not hold up other users. Temporary failures retry automatically. Unresolved accounts are revisited without treating unread calendars as empty; credential, delegation, Directory, and scope failures still require attention. + +Event edits, cancellations, access changes, inactive or removed users, and events moving outside the date window are reconciled in the background. Sim refreshes existing permissions while a longer crawl continues. Results with stale or unverified permissions remain hidden. Search is not a live Calendar read. ## Troubleshooting diff --git a/apps/docs/content/docs/search/google-drive.mdx b/apps/docs/content/docs/search/google-drive.mdx index 2dd2d0237e9..725d1cb7fcd 100644 --- a/apps/docs/content/docs/search/google-drive.mdx +++ b/apps/docs/content/docs/search/google-drive.mdx @@ -151,7 +151,7 @@ Use **Sync now** to request an update, **Pause syncing** to stop scheduled syncs Sim exports Docs and Slides as text and Sheets as XLSX spreadsheets. Supported uploaded files use the knowledge-base document pipeline, including PDF and Office formats. Unsupported files and oversized exports cannot be indexed; Google limits Workspace exports to 10 MB. See [Drive export formats](https://developers.google.com/workspace/drive/api/guides/ref-export-formats) and [download limits](https://developers.google.com/workspace/drive/api/guides/manage-downloads). -Search schedules syncs hourly. Central crawls revisit the selected users' files and permissions, including unchanged files, so permission changes and a new employee's older files are included. Unfinished crawls resume before deletion reconciliation. Content, deletions, and permissions refresh in the background; results are not a live read from Drive. **People** on the Sources settings page shows personal account connections; it does not list the central service-account credential. +Search schedules syncs hourly. Central crawls save each user's progress separately, so a large Drive or an account-specific access failure does not hold up other users. Temporary failures retry automatically. Sim continues discovering users and refreshing files and permissions while unfinished work resumes. Unread files are not treated as deleted, and results with stale or unverified permissions remain hidden. Results are not a live read from Drive. **People** on the Sources settings page shows personal account connections; it does not list the central service-account credential. ## Troubleshooting @@ -163,6 +163,7 @@ Directory permission warnings mean Sim could not fully verify group membership. | Directory access failed | Check all four delegated scopes and the **Directory administrator email** user's administrator privileges. A normal Google OAuth credential cannot supply this central Search path. | | Missing files in a central crawl | Check **Users**, folder and file-type filters, and whether selected active Workspace users can download the file and read its permissions. Opening a file alone does not prove either. Check Sync history for errors. Files reachable only by excluded or inactive accounts are not crawled; files with unverified permissions stay hidden. | | No text could be extracted | Images and scanned PDFs use OCR, but files with no extractable text cannot be indexed. Check whether the original contains readable text; a successful download does not guarantee searchable content. | +| File contains a Git LFS pointer | The uploaded file references content stored elsewhere instead of containing the actual image or PDF. Replace it in Drive with the original file content, then sync again. Retrying the unchanged pointer cannot recover the content. | | User not found or inactive | Use a primary email in the same Google Workspace customer. Aliases, external or guest accounts, suspended users, and archived users cannot be selected for crawling. | | A teammate sees no results | Confirm they have joined the Sim organization and their verified Sim email matches the Drive permission or group membership. For member accounts, finish their personal Drive connection too. | | A public or shared-link file is missing | Check **Openly shared files**. Link-only sharing does not grant Search access. A named user or group permission can still make the file searchable. | diff --git a/apps/sim/connectors/google-drive/company-crawl.test.ts b/apps/sim/connectors/google-drive/company-crawl.test.ts index 1633a255f98..79bc5e73ddb 100644 --- a/apps/sim/connectors/google-drive/company-crawl.test.ts +++ b/apps/sim/connectors/google-drive/company-crawl.test.ts @@ -553,6 +553,14 @@ describe('Google Drive company-wide crawl', () => { ).rejects.toThrow() expect(mockFetch).not.toHaveBeenCalled() }) + + it('passes the crawl signal into the delegated token exchange', async () => { + fixture({}) + const controller = new AbortController() + const ctx = { ...context(), signal: controller.signal } + await drive.listDocuments('directory-token', CONFIG, undefined, ctx) + expect(ctx.getDelegatedAccessToken).toHaveBeenCalledWith('alice@corp.com', controller.signal) + }) }) describe('Company-wide setup validation', () => { diff --git a/apps/sim/connectors/google-drive/company-crawl.ts b/apps/sim/connectors/google-drive/company-crawl.ts index 4a4815bc424..ea9fb997863 100644 --- a/apps/sim/connectors/google-drive/company-crawl.ts +++ b/apps/sim/connectors/google-drive/company-crawl.ts @@ -6,6 +6,10 @@ import { GOOGLE_WORKSPACE_DRIVES_PAGE_SIZE, listGoogleWorkspaceDrives, } from '@/connectors/google-drive/workspace-drives' +import { + type GoogleCompanyCursorAdapter, + googleCompanyUserContextSchema, +} from '@/connectors/google-workspace/company-work' import { GOOGLE_WORKSPACE_USERS_PAGE_SIZE, getGoogleWorkspaceUser, @@ -18,15 +22,7 @@ import { parseOptionalUnlimitedSafeInteger } from '@/connectors/utils' const CURSOR_PREFIX = 'gdrive-company:v1:' const MAX_CURSOR_BYTES = 384 * 1024 const cursorSchema = z.object({ - users: z - .array( - z.object({ - id: z.string().min(1).max(256), - email: z.string().email().max(254), - customerId: z.string().min(1).max(256), - }) - ) - .max(GOOGLE_WORKSPACE_USERS_PAGE_SIZE), + users: z.array(googleCompanyUserContextSchema).max(GOOGLE_WORKSPACE_USERS_PAGE_SIZE), nextUsersPageToken: z.string().min(1).max(8192).optional(), scope: z.discriminatedUnion('kind', [ z.object({ @@ -52,7 +48,7 @@ const cursorSchema = z.object({ }) type CompanyCursor = z.infer -type DelegatedTokenResolver = (subject: string) => Promise +type DelegatedTokenResolver = (subject: string, signal?: AbortSignal) => Promise function delegatedTokenResolver(syncContext: Record): DelegatedTokenResolver { if (typeof syncContext.getDelegatedAccessToken !== 'function') { @@ -88,6 +84,21 @@ function writeCursor(cursor: CompanyCursor): string { return serialized } +/** A single-user cursor preserves shared-drive traversal as well as the user's own Drive page. */ +export const googleDriveCompanyCursorAdapter: GoogleCompanyCursorAdapter = { + seed: (user) => writeCursor({ users: [user], scope: { kind: 'user' } }), + resume: (cursor) => { + const state = readCursor(cursor) + return state.users.map((user, index) => ({ + user, + cursor: writeCursor({ + users: [user], + scope: index === 0 ? state.scope : { kind: 'user' }, + }), + })) + }, +} + /** Positive caps can stop before later users; central sources must finish their selected corpus. */ function validateCompanyConfig(sourceConfig: Record): string[] { if ( @@ -197,7 +208,7 @@ export async function listGoogleCompanyDocuments(input: { } signal?.throwIfAborted() - const userToken = await resolveToken(user.email) + const userToken = await (signal ? resolveToken(user.email, signal) : resolveToken(user.email)) const nextDrives = async (pageToken?: string): Promise => { const drives = await listGoogleWorkspaceDrives(userToken, pageToken, signal) if (drives.driveIds.length > 0) { diff --git a/apps/sim/connectors/google-drive/google-drive-errors.ts b/apps/sim/connectors/google-drive/google-drive-errors.ts index 141a0195906..b26749a4155 100644 --- a/apps/sim/connectors/google-drive/google-drive-errors.ts +++ b/apps/sim/connectors/google-drive/google-drive-errors.ts @@ -12,6 +12,7 @@ import { import { ConnectorSourceError, type ConnectorSourceFailureCategory, + type ConnectorSourceReasonState, } from '@/connectors/source-error' const GOOGLE_ERROR_REASON_MAX_COUNT = 16 @@ -110,7 +111,8 @@ export class GoogleDriveApiError extends ConnectorSourceError { status: number, normalizedReasons: readonly string[], operation = 'drive.request', - reasonsComplete = true + reasonsComplete = true, + reasonState?: ConnectorSourceReasonState ) { const diagnosticReasons = safeGoogleErrorReasons(normalizedReasons).slice( 0, @@ -122,7 +124,7 @@ export class GoogleDriveApiError extends ConnectorSourceError { `Google Drive API request failed with HTTP ${status}${reasonSuffix}`, status, diagnosticCategory(kind, status), - { operation, reasons: diagnosticReasons } + { operation, reasons: diagnosticReasons, ...(reasonState ? { reasonState } : {}) } ) this.name = 'GoogleDriveApiError' this.reasons = diagnosticReasons @@ -144,7 +146,13 @@ export async function readGoogleDriveApiError( operation = 'drive.request' ): Promise { const details = await readGoogleErrorDetails(response) - return new GoogleDriveApiError(response.status, details.reasons, operation, details.complete) + return new GoogleDriveApiError( + response.status, + details.reasons, + operation, + details.complete, + details.reasonState + ) } /** diff --git a/apps/sim/connectors/google-workspace/api-errors.test.ts b/apps/sim/connectors/google-workspace/api-errors.test.ts index 04ca5ec08ce..fd14c8efcae 100644 --- a/apps/sim/connectors/google-workspace/api-errors.test.ts +++ b/apps/sim/connectors/google-workspace/api-errors.test.ts @@ -32,6 +32,7 @@ describe('Google API diagnostics', () => { new Error('outer private detail', { cause: error }) ) expect(diagnostic).toMatchObject({ status, category, operation: OPERATION, reasons: [reason] }) + expect(diagnostic?.reasonState).toBe('present') expect(JSON.stringify(diagnostic)).not.toContain(RESPONSE_SECRET) expect(JSON.stringify(diagnostic)).not.toContain('outer private detail') }) @@ -40,6 +41,7 @@ describe('Google API diagnostics', () => { const error = await readGoogleApiError(failure(403, RESPONSE_SECRET), OPERATION) expect(error.diagnostic?.reasons).toEqual([]) expect(error.reasonsComplete).toBe(false) + expect(getConnectorFailureDiagnostic(error)?.reasonState).toBe('filtered') expect(JSON.stringify(error)).not.toContain(RESPONSE_SECRET) }) @@ -82,11 +84,13 @@ describe('Google API diagnostics', () => { ) expect(absent.reasonsComplete).toBe(true) expect(absent.diagnostic?.reasons).toEqual([]) + expect(getConnectorFailureDiagnostic(absent)?.reasonState).toBe('absent') const malformed = await readGoogleApiError( Response.json({ error: { errors: [{ reason: 123 }] } }, { status: 403 }), 'calendar.events.list' ) expect(malformed.reasonsComplete).toBe(false) + expect(getConnectorFailureDiagnostic(malformed)?.reasonState).toBe('malformed') const mixed = await readGoogleApiError( Response.json( { error: { errors: [{ reason: 'forbidden' }, { reason: RESPONSE_SECRET }] } }, @@ -96,6 +100,7 @@ describe('Google API diagnostics', () => { ) expect(mixed.diagnostic?.reasons).toEqual(['forbidden']) expect(mixed.reasonsComplete).toBe(false) + expect(getConnectorFailureDiagnostic(mixed)?.reasonState).toBe('filtered') expect(JSON.stringify([absent, malformed, mixed])).not.toContain(RESPONSE_SECRET) }) @@ -120,8 +125,61 @@ describe('Google API diagnostics', () => { ) expect(error.diagnostic?.reasons).toEqual(['forbidden']) expect(error.reasonsComplete).toBe(false) + expect(getConnectorFailureDiagnostic(error)?.reasonState).toBe('malformed') expect(JSON.stringify(error)).not.toContain(RESPONSE_SECRET) }) + + it('distinguishes an unreadable body from a malformed response', async () => { + const unreadable = new Response( + new ReadableStream({ + start(controller) { + controller.error(new Error(RESPONSE_SECRET)) + }, + }), + { status: 403 } + ) + const error = await readGoogleApiError(unreadable, OPERATION) + expect(getConnectorFailureDiagnostic(error)?.reasonState).toBe('unreadable') + expect(error.reasonsComplete).toBe(false) + expect(JSON.stringify(error)).not.toContain(RESPONSE_SECRET) + const malformed = await readGoogleApiError(new Response('not-json', { status: 403 }), OPERATION) + expect(getConnectorFailureDiagnostic(malformed)?.reasonState).toBe('malformed') + }) + + it('retains classification evidence beyond the diagnostic reason limit', async () => { + const reasons = [ + 'accessNotConfigured', + 'appNotAuthorizedToFile', + 'authError', + 'badRequest', + 'cannotDownloadFile', + 'cannotExportFile', + 'domainPolicy', + 'download_restricted_for_revision', + 'exportSizeLimitExceeded', + 'failedPrecondition', + 'fileNotDownloadable', + 'fileNotExportable', + 'forbidden', + 'insufficientFilePermissions', + 'insufficientPermissions', + 'invalid', + 'rateLimitExceeded', + ] + const error = await readGoogleApiError( + Response.json( + { + error: { errors: reasons.map((reason) => ({ reason })) }, + }, + { status: 403 } + ), + OPERATION + ) + expect(error.rateLimited).toBe(true) + expect(error.reasonsComplete).toBe(false) + expect(error.diagnostic?.reasonState).toBe('filtered') + expect(error.diagnostic?.reasons).toHaveLength(16) + }) }) describe('Google API retries', () => { diff --git a/apps/sim/connectors/google-workspace/api-errors.ts b/apps/sim/connectors/google-workspace/api-errors.ts index e5c16f75b02..c9f43e157b0 100644 --- a/apps/sim/connectors/google-workspace/api-errors.ts +++ b/apps/sim/connectors/google-workspace/api-errors.ts @@ -5,7 +5,7 @@ import { type RetryOptions, resolveRetryDelayMs, } from '@/lib/knowledge/documents/utils' -import { ConnectorSourceError } from '@/connectors/source-error' +import { ConnectorSourceError, type ConnectorSourceReasonState } from '@/connectors/source-error' import { readBodyWithLimit } from '@/connectors/utils' const ERROR_BODY_MAX_BYTES = 64 * 1024 @@ -60,15 +60,16 @@ export function safeGoogleErrorReasons(reasons: readonly string[]): string[] { /** Distinguishes a reasonless envelope from one whose reason codes cannot safely be interpreted. */ export async function readGoogleErrorDetails( response: Response -): Promise<{ reasons: string[]; complete: boolean }> { - const unreadable = { reasons: [], complete: false } +): Promise<{ reasons: string[]; complete: boolean; reasonState: ConnectorSourceReasonState }> { + const unreadable = { reasons: [], complete: false, reasonState: 'unreadable' as const } + const malformed = { reasons: [], complete: false, reasonState: 'malformed' as const } const body = await readBodyWithLimit(response, ERROR_BODY_MAX_BYTES).catch(() => null) if (!body) return unreadable try { const payload: unknown = JSON.parse(body.toString('utf8')) - if (!payload || typeof payload !== 'object' || !('error' in payload)) return unreadable + if (!payload || typeof payload !== 'object' || !('error' in payload)) return malformed const error = payload.error - if (!error || typeof error !== 'object' || Array.isArray(error)) return unreadable + if (!error || typeof error !== 'object' || Array.isArray(error)) return malformed const envelopeValid = (!('errors' in error) || Array.isArray(error.errors)) && (!('details' in error) || Array.isArray(error.details)) @@ -82,17 +83,24 @@ export async function readGoogleErrorDetails( : [] ) const safeReasons = safeGoogleErrorReasons(reasons) + const validReasons = + envelopeValid && + (entries.length > 0 || ('code' in error && error.code === response.status)) && + reasons.length === entries.length + const reasonState: ConnectorSourceReasonState = !validReasons + ? 'malformed' + : reasons.some((reason) => !SAFE_REASONS.has(reason)) || safeReasons.length > MAX_REASONS + ? 'filtered' + : reasons.length === 0 + ? 'absent' + : 'present' return { reasons: safeReasons, - complete: - envelopeValid && - (entries.length > 0 || ('code' in error && error.code === response.status)) && - reasons.length === entries.length && - reasons.every((reason) => SAFE_REASONS.has(reason)) && - safeReasons.length <= MAX_REASONS, + complete: reasonState === 'present' || reasonState === 'absent', + reasonState, } } catch { - return unreadable + return malformed } } @@ -104,7 +112,8 @@ export class GoogleApiError extends ConnectorSourceError { operation: string, status: number, reasons: readonly string[], - reasonsComplete = true + reasonsComplete = true, + reasonState?: ConnectorSourceReasonState ) { const safeReasons = safeGoogleErrorReasons(reasons) const suffix = safeReasons.length ? ` (${safeReasons.join(', ')})` : '' @@ -123,6 +132,7 @@ export class GoogleApiError extends ConnectorSourceError { super(`${operation} failed (HTTP ${status})${suffix}.`, status, category, { operation, reasons: safeReasons.slice(0, MAX_REASONS), + ...(reasonState ? { reasonState } : {}), }) this.name = 'GoogleApiError' this.reasonsComplete = @@ -139,7 +149,13 @@ export async function readGoogleApiError( operation: string ): Promise { const details = await readGoogleErrorDetails(response) - return new GoogleApiError(operation, response.status, details.reasons, details.complete) + return new GoogleApiError( + operation, + response.status, + details.reasons, + details.complete, + details.reasonState + ) } /** Preserves Google diagnostics when the shared transport retries a transient HTTP response. */ diff --git a/apps/sim/connectors/google-workspace/company-crawl.test.ts b/apps/sim/connectors/google-workspace/company-crawl.test.ts index 0c8c5c16041..cc4bfd3b715 100644 --- a/apps/sim/connectors/google-workspace/company-crawl.test.ts +++ b/apps/sim/connectors/google-workspace/company-crawl.test.ts @@ -349,6 +349,7 @@ describe('Google Workspace per-user central crawl', () => { } const calls = listUserDocuments.mock.calls.length await expect(list(ctx2, first.currentCursor)).rejects.toThrow() + expect(ctx2.getDelegatedAccessToken).toHaveBeenCalledWith('alice@corp.com', controller2.signal) expect(listUserDocuments).toHaveBeenCalledTimes(calls) }) diff --git a/apps/sim/connectors/google-workspace/company-crawl.ts b/apps/sim/connectors/google-workspace/company-crawl.ts index 3a354ad36f3..6202173cee9 100644 --- a/apps/sim/connectors/google-workspace/company-crawl.ts +++ b/apps/sim/connectors/google-workspace/company-crawl.ts @@ -3,6 +3,10 @@ import { normalizeEmail } from '@sim/utils/string' import { z } from 'zod' import { mapWithConcurrency } from '@/lib/core/utils/concurrency' import { GoogleApiError } from '@/connectors/google-workspace/api-errors' +import { + type GoogleCompanyCursorAdapter, + googleCompanyUserContextSchema, +} from '@/connectors/google-workspace/company-work' import { GOOGLE_WORKSPACE_USERS_PAGE_SIZE, type GoogleWorkspaceUser, @@ -28,15 +32,7 @@ const MAX_PROVIDER_CURSOR_BYTES = 256 * 1024 const MAX_PAGE_DOCUMENTS = 2500 const cursorSchema = z.object({ provider: z.enum(['gmail', 'google_calendar']), - users: z - .array( - z.object({ - id: z.string().min(1).max(256), - email: z.string().email().max(254), - customerId: z.string().min(1).max(256), - }) - ) - .max(GOOGLE_WORKSPACE_USERS_PAGE_SIZE), + users: z.array(googleCompanyUserContextSchema).max(GOOGLE_WORKSPACE_USERS_PAGE_SIZE), nextUsersPageToken: z.string().min(1).max(8192).optional(), providerCursor: z.string().min(1).max(MAX_PROVIDER_CURSOR_BYTES).optional(), listingFailures: listingFailuresSchema.optional(), @@ -91,15 +87,40 @@ function writeCursor(state: CompanyCursor): string { return cursor } +/** Reuses the verified per-user crawl without placing every user's continuation in one cursor. */ +export function googleWorkspaceCompanyCursorAdapter( + provider: GoogleWorkspaceProvider +): GoogleCompanyCursorAdapter { + return { + seed: (user) => writeCursor({ provider, users: [user] }), + resume: (cursor) => { + const state = readCursor(cursor, provider) + return state.users.map((user, index) => ({ + user, + cursor: writeCursor({ + provider, + users: [user], + ...(index === 0 ? { providerCursor: state.providerCursor } : {}), + }), + })) + }, + } +} + function signalFrom(context: Record): AbortSignal | undefined { return context.signal instanceof AbortSignal ? context.signal : undefined } -function tokenResolver(context: Record): (subject: string) => Promise { +function tokenResolver( + context: Record +): (subject: string, signal?: AbortSignal) => Promise { if (context.mirrorsSourceAcls !== true || typeof context.getDelegatedAccessToken !== 'function') { throw new Error('Company-wide indexing requires a delegated Google Workspace service account') } - return context.getDelegatedAccessToken as (subject: string) => Promise + return context.getDelegatedAccessToken as ( + subject: string, + signal?: AbortSignal + ) => Promise } function userContext( @@ -117,9 +138,11 @@ async function delegate( user: GoogleWorkspaceUser, context: Record ): Promise { - signalFrom(context)?.throwIfAborted() - const token = await tokenResolver(context)(user.email) - signalFrom(context)?.throwIfAborted() + const signal = signalFrom(context) + signal?.throwIfAborted() + const resolveToken = tokenResolver(context) + const token = await (signal ? resolveToken(user.email, signal) : resolveToken(user.email)) + signal?.throwIfAborted() if (typeof token !== 'string' || !token) throw new Error('Google Workspace delegation returned no access token') return token diff --git a/apps/sim/connectors/google-workspace/company-work.ts b/apps/sim/connectors/google-workspace/company-work.ts new file mode 100644 index 00000000000..8e33d8ac2f8 --- /dev/null +++ b/apps/sim/connectors/google-workspace/company-work.ts @@ -0,0 +1,22 @@ +import { z } from 'zod' +import type { GoogleWorkspaceUser } from '@/connectors/google-workspace/users' + +/** Directory identity and provider continuation are persisted separately from delegated credentials. */ +export interface GoogleCompanyUserWork { + user: Pick + cursor?: string +} + +/** Seeds per-user provider cursors and adopts legacy crawls without losing the active continuation. */ +export interface GoogleCompanyCursorAdapter { + seed: (user: GoogleCompanyUserWork['user']) => string + /** Retains the exact active page when adopting the former single-user-at-a-time cursor. */ + resume: (cursor: string) => GoogleCompanyUserWork[] +} + +/** Validates Google Directory identity before persisted partition context is used for delegation. */ +export const googleCompanyUserContextSchema = z.object({ + id: z.string().min(1).max(256), + email: z.string().email().max(254), + customerId: z.string().min(1).max(256), +}) diff --git a/apps/sim/connectors/listing-failures.test.ts b/apps/sim/connectors/listing-failures.test.ts new file mode 100644 index 00000000000..18fd0af498c --- /dev/null +++ b/apps/sim/connectors/listing-failures.test.ts @@ -0,0 +1,56 @@ +/** @vitest-environment node */ +import { describe, expect, it } from 'vitest' +import { + beginListingCheckpoint, + readListingCheckpoint, +} from '@/lib/knowledge/connectors/listing-checkpoint' +import { listingFailuresSchema } from '@/connectors/listing-failures' +import { CONNECTOR_SOURCE_REASON_STATES } from '@/connectors/source-error' + +const sample = { + scope: 'user@fixture.test', + operation: 'calendar.events.list', + status: 403, + reasons: [], +} + +describe('listing failure diagnostics', () => { + it.each(CONNECTOR_SOURCE_REASON_STATES)( + 'preserves %s reason evidence through durable checkpoint serialization', + (reasonState) => { + const fingerprint = 'a'.repeat(64) + const checkpoint = { + ...beginListingCheckpoint({ + fingerprint, + generationId: 'generation', + startedAt: new Date('2026-09-17T00:00:00Z'), + }), + listingFailures: { count: 1, samples: [{ ...sample, reasonState }] }, + } + expect( + readListingCheckpoint(structuredClone(checkpoint), fingerprint)?.listingFailures + ).toEqual(checkpoint.listingFailures) + } + ) + + it('accepts existing samples without reason state and rejects arbitrary new state strings', () => { + expect(listingFailuresSchema.parse({ count: 1, samples: [sample] })).toEqual({ + count: 1, + samples: [sample], + }) + expect( + listingFailuresSchema.safeParse({ + count: 1, + samples: [{ ...sample, reasonState: 'provider-private-description' }], + }).success + ).toBe(false) + }) + + it('retains the enum without persisting attached provider messages or response bodies', () => { + const result = listingFailuresSchema.parse({ + count: 1, + samples: [{ ...sample, reasonState: 'filtered', message: 'private', body: 'private' }], + }) + expect(result.samples[0]).toEqual({ ...sample, reasonState: 'filtered' }) + }) +}) diff --git a/apps/sim/connectors/listing-failures.ts b/apps/sim/connectors/listing-failures.ts index d64e85bfd82..22dedb2e9d1 100644 --- a/apps/sim/connectors/listing-failures.ts +++ b/apps/sim/connectors/listing-failures.ts @@ -1,4 +1,5 @@ import { z } from 'zod' +import { CONNECTOR_SOURCE_REASON_STATES } from '@/connectors/source-error' export const MAX_LISTING_FAILURE_SAMPLES = 10 @@ -12,6 +13,7 @@ export const listingFailuresSchema = z.object({ operation: z.string().min(1).max(96), status: z.number().int().min(100).max(599).optional(), reasons: z.array(z.string().min(1).max(64)).max(16), + reasonState: z.enum(CONNECTOR_SOURCE_REASON_STATES).optional(), }) ) .max(MAX_LISTING_FAILURE_SAMPLES), diff --git a/apps/sim/connectors/source-error.ts b/apps/sim/connectors/source-error.ts index 37e28189674..9b7f2184fc4 100644 --- a/apps/sim/connectors/source-error.ts +++ b/apps/sim/connectors/source-error.ts @@ -6,13 +6,28 @@ export type ConnectorSourceFailureCategory = | 'rate_limit' | 'provider_unavailable' +/** Whether provider reason codes were present and safe to retain. */ +export const CONNECTOR_SOURCE_REASON_STATES = [ + 'present', + 'absent', + 'filtered', + 'malformed', + 'unreadable', +] as const + +export type ConnectorSourceReasonState = (typeof CONNECTOR_SOURCE_REASON_STATES)[number] + /** Providers classify their structured reasons here; shared diagnostics own user-facing text. */ export class ConnectorSourceError extends Error { constructor( message: string, readonly status: number, readonly category?: ConnectorSourceFailureCategory, - readonly diagnostic?: { operation: string; reasons: readonly string[] } + readonly diagnostic?: { + operation: string + reasons: readonly string[] + reasonState?: ConnectorSourceReasonState + } ) { super(message) this.name = 'ConnectorSourceError' diff --git a/apps/sim/connectors/types.ts b/apps/sim/connectors/types.ts index 9586bad3c79..5c5bc3f49b0 100644 --- a/apps/sim/connectors/types.ts +++ b/apps/sim/connectors/types.ts @@ -3,6 +3,7 @@ import type { ConnectorAccessMode } from '@/lib/knowledge/connectors/access-mode import type { ConnectorPermissionConfigCapability } from '@/lib/knowledge/connectors/permission-config' import type { OAuthService } from '@/lib/oauth/types' import type { SelectorKey } from '@/lib/selectors/manifest' +import type { ConnectorSourceReasonState } from '@/connectors/source-error' /** * Authentication configuration for a connector. @@ -190,6 +191,7 @@ export interface ExternalListingFailures { operation: string status?: number reasons: string[] + reasonState?: ConnectorSourceReasonState }[] } @@ -207,7 +209,11 @@ export interface ExternalDocumentList { */ reconciliationSafe?: boolean /** Cumulative, bounded failure evidence for this listing generation; replay must not add it twice. */ - listingFailures?: ExternalListingFailures + listingFailures?: ExternalListingFailures | null + /** Refreshes existing permissions and repairs changed stored bodies, without discovering new content or reconciling absence. */ + permissionsOnly?: boolean + /** A durable user-work queue can yield until the next bounded retry becomes due. */ + resumeAt?: string } /** diff --git a/apps/sim/lib/knowledge/__integration__/connector-lifecycle-locks.integration.ts b/apps/sim/lib/knowledge/__integration__/connector-lifecycle-locks.integration.ts new file mode 100644 index 00000000000..8d59264625a --- /dev/null +++ b/apps/sim/lib/knowledge/__integration__/connector-lifecycle-locks.integration.ts @@ -0,0 +1,262 @@ +/** Real PostgreSQL contention and deletion exclusion through source lifecycle entry points. */ +import { db } from '@sim/db' +import { + document, + knowledgeBase, + knowledgeConnector, + knowledgeConnectorSyncLog, + organization, + user, + workspace, +} from '@sim/db/schema' +import { generateId } from '@sim/utils/id' +import { and, eq, inArray, isNotNull, sql } from 'drizzle-orm' +import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import type { DbTransaction } from '@/lib/db/types' + +vi.mock('@/lib/knowledge/documents/service', async (original) => ({ + ...(await original()), + processDocumentsWithQueue: vi.fn(async (documents: unknown[]) => ({ + requested: documents.length, + accepted: documents.length, + failed: 0, + })), +})) + +import { resolveBillingAttribution } from '@/lib/billing/core/billing-attribution' +import { + createKnowledgeAclFixtureIds, + seedKnowledgeAclFixture, +} from '@/lib/knowledge/__integration__/seed-source-access-fixture' +import { deferConnectorSync } from '@/lib/knowledge/connectors/sync-deferral' +import { completeSuccessfulSync } from '@/lib/knowledge/connectors/sync-engine' +import { createContentSyncLease } from '@/lib/knowledge/connectors/sync-lock' +import { sweepStuckDocuments } from '@/lib/knowledge/connectors/sync-primitives' +import { deleteKnowledgeBase } from '@/lib/knowledge/service' +import { GitHubRequestDeferredError } from '@/connectors/github/request' +import type { SyncResult } from '@/connectors/types' + +const WAIT_OPTIONS = { interval: 5, timeout: 5000 } +const ACTIONS = ['complete', 'defer', 'recover'] as const +type Action = (typeof ACTIONS)[number] + +function deferred() { + let resolve!: () => void + const promise = new Promise((ready) => { + resolve = ready + }) + return { promise, resolve } +} + +function emptyResult(): SyncResult { + return { + docsAdded: 0, + docsUpdated: 0, + docsDeleted: 0, + docsUnchanged: 0, + docsSkipped: 0, + docsFailed: 0, + processingDispatch: { requested: 0, accepted: 0, failed: 0 }, + } +} + +describe('source lifecycle KB guards', () => { + let ids: ReturnType + let billing: Awaited> + let retryDocumentId: string + + beforeEach(async () => { + ids = createKnowledgeAclFixtureIds() + await seedKnowledgeAclFixture(ids) + billing = await resolveBillingAttribution({ + actorUserId: ids.aliceId, + workspaceId: ids.workspaceId, + }) + await db.insert(knowledgeConnectorSyncLog).values({ + id: ids.lockId, + connectorId: ids.connectorId, + status: 'started', + }) + retryDocumentId = generateId() + const old = new Date(Date.now() - 24 * 60 * 60_000) + await db.insert(document).values({ + id: retryDocumentId, + knowledgeBaseId: ids.knowledgeBaseId, + connectorId: ids.connectorId, + filename: 'Retained fixture.txt', + fileUrl: 'data:text/plain,fixture', + fileSize: 7, + mimeType: 'text/plain', + contentHash: 'fixture', + storageKey: 'fixture-retained', + processingStatus: 'failed', + processingAttempts: 1, + processingCompletedAt: old, + uploadedAt: old, + }) + }) + afterEach(async () => { + vi.restoreAllMocks() + await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await db.delete(organization).where(eq(organization.id, ids.organizationId)) + await db.delete(user).where(inArray(user.id, [ids.aliceId, ids.bobId])) + }) + afterAll(async () => { + await db.$client.end() + }) + + function run(action: Action) { + const result = emptyResult() + const lease = createContentSyncLease(ids.connectorId, ids.lockId) + if (action === 'complete') { + return completeSuccessfulSync( + ids.connectorId, + ids.knowledgeBaseId, + ids.lockId, + 60, + result, + null + ) + } + if (action === 'defer') { + return deferConnectorSync({ + connectorId: ids.connectorId, + knowledgeBaseId: ids.knowledgeBaseId, + runId: ids.lockId, + lease, + kind: 'content', + result, + error: new GitHubRequestDeferredError(60_000), + }) + } + return sweepStuckDocuments({ + connectorId: ids.connectorId, + knowledgeBaseId: ids.knowledgeBaseId, + syncStartedAt: new Date(), + retryCutoff: new Date(Date.now() - 7 * 24 * 60 * 60_000), + billingAttribution: billing, + result, + lease, + }) + } + + async function wrote(action: Action, tx: DbTransaction) { + if (action === 'recover') { + const [row] = await tx + .select({ status: document.processingStatus }) + .from(document) + .where(eq(document.id, retryDocumentId)) + return row.status === 'pending' + } + const [row] = await tx + .select({ status: knowledgeConnectorSyncLog.status }) + .from(knowledgeConnectorSyncLog) + .where(eq(knowledgeConnectorSyncLog.id, ids.lockId)) + return row.status !== 'started' + } + + function holdCommit(action: Action) { + const gate = { pid: undefined as number | undefined, release: deferred() } + const transaction = db.transaction.bind(db) + vi.spyOn(db, 'transaction').mockImplementation((callback, config) => + transaction(async (tx) => { + await tx.execute(sql`SET LOCAL statement_timeout = '10s'`) + await tx.execute(sql`SET LOCAL idle_in_transaction_session_timeout = '15s'`) + const result = await callback(tx) + if (gate.pid === undefined && (await wrote(action, tx))) { + const [backend] = await tx.execute<{ pid: number }>(sql`SELECT pg_backend_pid() AS pid`) + gate.pid = backend.pid + await gate.release.promise + } + return result + }, config) + ) + return gate + } + + it.each(ACTIONS)( + '%s can commit while an unrelated embedding holds the KB key-share lock', + async (action) => { + const release = deferred() + const acquired = deferred() + const holding = db.transaction(async (tx) => { + await tx.execute(sql`SET LOCAL idle_in_transaction_session_timeout = '15s'`) + await tx + .select({ id: knowledgeBase.id }) + .from(knowledgeBase) + .where(eq(knowledgeBase.id, ids.knowledgeBaseId)) + .for('key share') + acquired.resolve() + await release.promise + }) + await acquired.promise + const completed = Promise.allSettled([run(action)]) + let settled = false + void completed.then(() => { + settled = true + }) + try { + await expect.poll(() => settled, WAIT_OPTIONS).toBe(true) + expect(await completed).toMatchObject([{ status: 'fulfilled' }]) + } finally { + release.resolve() + await holding + await completed + } + } + ) + + it.each(ACTIONS)('%s keeps soft deletion excluded until its commit', async (action) => { + const gate = holdCommit(action) + const completed = Promise.allSettled([run(action)]) + let deleted: Promise[]> | undefined + try { + await expect.poll(() => gate.pid, WAIT_OPTIONS).toBeDefined() + deleted = Promise.allSettled([deleteKnowledgeBase(ids.knowledgeBaseId, 'fixture-delete')]) + await expect + .poll(async () => { + const [waiter] = await db.execute<{ pid: number }>(sql` + SELECT pid FROM pg_stat_activity WHERE datname = current_database() + AND wait_event_type = 'Lock' AND ${gate.pid!} = ANY(pg_blocking_pids(pid)) LIMIT 1 + `) + return waiter?.pid + }, WAIT_OPTIONS) + .toBeDefined() + const [active] = await db + .select({ deletedAt: knowledgeBase.deletedAt }) + .from(knowledgeBase) + .where(eq(knowledgeBase.id, ids.knowledgeBaseId)) + expect(active.deletedAt).toBeNull() + gate.release.resolve() + expect(await completed).toMatchObject([{ status: 'fulfilled' }]) + expect(await deleted).toMatchObject([{ status: 'fulfilled' }]) + const [archived] = await db + .select({ id: document.id }) + .from(document) + .where(and(eq(document.id, retryDocumentId), isNotNull(document.archivedAt))) + expect(archived?.id).toBe(retryDocumentId) + } finally { + gate.release.resolve() + await completed + await deleted + } + }) + + it.each(ACTIONS)('%s cannot commit writes from a replaced connector lease', async (action) => { + await db + .update(knowledgeConnector) + .set({ syncLockToken: generateId() }) + .where(eq(knowledgeConnector.id, ids.connectorId)) + await Promise.allSettled([run(action)]) + const [log] = await db + .select({ status: knowledgeConnectorSyncLog.status }) + .from(knowledgeConnectorSyncLog) + .where(eq(knowledgeConnectorSyncLog.id, ids.lockId)) + expect(log.status).toBe('started') + const [unchanged] = await db + .select({ status: document.processingStatus }) + .from(document) + .where(eq(document.id, retryDocumentId)) + expect(unchanged.status).toBe('failed') + }) +}) diff --git a/apps/sim/lib/knowledge/__integration__/connector-partition-work.integration.ts b/apps/sim/lib/knowledge/__integration__/connector-partition-work.integration.ts new file mode 100644 index 00000000000..1b6d75b2985 --- /dev/null +++ b/apps/sim/lib/knowledge/__integration__/connector-partition-work.integration.ts @@ -0,0 +1,539 @@ +/** Real PostgreSQL coverage for provider-independent partition work and atomic checkpoint advancement. */ +import { db } from '@sim/db' +import { + knowledgeConnector, + knowledgeConnectorPartition, + organization, + user, + workspace, +} from '@sim/db/schema' +import { and, eq, inArray, sql } from 'drizzle-orm' +import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { z } from 'zod' +import { seedKnowledgeAclFixture } from '@/lib/knowledge/__integration__/seed-source-access-fixture' +import { beginListingCheckpoint } from '@/lib/knowledge/connectors/listing-checkpoint' +import { + commitConnectorPartitionWork, + connectorPartitionWorkStore, +} from '@/lib/knowledge/connectors/partition-store' +import type { ConnectorPartitionWorkUpdate } from '@/lib/knowledge/connectors/partition-work' +import { assertSyncLeaseHeldInTx, stillHoldsSyncLock } from '@/lib/knowledge/connectors/sync-lock' + +const spaceContextSchema = z + .object({ + spaceKey: z.string().min(1).max(128), + siteId: z.string().min(1).max(256), + }) + .strict() +const parseContext = (value: unknown) => spaceContextSchema.parse(value) + +describe('Connector partition checkpoint storage', () => { + let owner: Awaited> + const generationId = 'partition-fixture-generation' + const generationStartedAt = new Date(Date.now() - 13 * 60 * 60 * 1000) + const permissionRefreshAt = new Date(generationStartedAt.getTime() + 12 * 60 * 60_000) + const first = { + partitionKey: 'first', + context: { spaceKey: 'FIRST', siteId: 'fixture-site' }, + cursor: 'saved-provider-page-91', + } + const second = { + partitionKey: 'second', + context: { spaceKey: 'SECOND', siteId: 'fixture-site' }, + cursor: 'provider-first-page', + } + const checkpoint = beginListingCheckpoint({ + fingerprint: 'a'.repeat(64), + generationId, + startedAt: generationStartedAt, + }) + + beforeEach(async () => { + owner = await seedKnowledgeAclFixture(undefined, { connectorType: 'confluence' }) + await db.transaction(async (tx) => { + await commitConnectorPartitionWork( + tx, + owner.connectorId, + generationId, + { enqueue: [first, second] }, + permissionRefreshAt + ) + await tx + .update(knowledgeConnector) + .set({ listingCheckpoint: checkpoint }) + .where(eq(knowledgeConnector.id, owner.connectorId)) + }) + }) + afterEach(async () => { + vi.unstubAllEnvs() + await db.delete(workspace).where(eq(workspace.id, owner.workspaceId)) + await db.delete(organization).where(eq(organization.id, owner.organizationId)) + await db.delete(user).where(inArray(user.id, [owner.aliceId, owner.bobId])) + }) + afterAll(() => db.$client.end()) + + it('round-trips provider-owned space metadata without Google identity fields', async () => { + const store = connectorPartitionWorkStore(owner.connectorId, generationId, parseContext) + expect(await store.get('first', 'content')).toMatchObject({ + partitionKey: first.partitionKey, + context: first.context, + cursor: first.cursor, + }) + expect((await store.next('content', new Date()))?.context).toEqual(first.context) + const [row] = await db + .select({ permissionRetryAt: knowledgeConnectorPartition.permissionRetryAt }) + .from(knowledgeConnectorPartition) + .where( + and( + eq(knowledgeConnectorPartition.connectorId, owner.connectorId), + eq(knowledgeConnectorPartition.partitionKey, first.partitionKey) + ) + ) + expect(row.permissionRetryAt).toEqual(permissionRefreshAt) + }) + + it('rejects malformed stored provider metadata before exposing work to its caller', async () => { + await db + .update(knowledgeConnectorPartition) + .set({ context: { spaceKey: 7, siteId: 'fixture-site' } }) + .where(eq(knowledgeConnectorPartition.connectorId, owner.connectorId)) + const store = connectorPartitionWorkStore(owner.connectorId, generationId, parseContext) + await expect(store.get('first', 'content')).rejects.toMatchObject({ name: 'ZodError' }) + await expect(store.next('content', new Date())).rejects.toMatchObject({ name: 'ZodError' }) + }) + + it.each([ + { label: 'null', context: null }, + { label: 'array', context: [] }, + { label: 'string', context: 'not-an-object' }, + { label: 'number', context: 7 }, + ])('rejects non-object JSON metadata: $label', async ({ context }) => { + await expect( + db.execute(sql` + UPDATE ${knowledgeConnectorPartition} + SET context = ${JSON.stringify(context)}::jsonb + WHERE connector_id = ${owner.connectorId} + `) + ).rejects.toMatchObject({ cause: { code: '23514', constraint_name: 'kcp_context_check' } }) + }) + + it('requires context rather than silently inserting empty metadata', async () => { + await expect( + db.execute(sql` + INSERT INTO ${knowledgeConnectorPartition} + (connector_id, partition_key, generation_id, permission_retry_at) + VALUES (${owner.connectorId}, 'missing-context', ${generationId}, now()) + `) + ).rejects.toMatchObject({ cause: { code: '23502', column_name: 'context' } }) + }) + + it('accepts the context byte limit and rejects larger ASCII and multibyte objects', async () => { + await db.execute(sql` + UPDATE ${knowledgeConnectorPartition} + SET context = jsonb_build_object( + 'payload', repeat('a', 16384 - octet_length(jsonb_build_object('payload', '')::text)) + ) + WHERE connector_id = ${owner.connectorId} + `) + const [row] = await db + .select({ bytes: sql`octet_length(${knowledgeConnectorPartition.context}::text)` }) + .from(knowledgeConnectorPartition) + .where(eq(knowledgeConnectorPartition.connectorId, owner.connectorId)) + .limit(1) + expect(row.bytes).toBe(16384) + for (const payload of ['a'.repeat(16384), 'あ'.repeat(5500)]) { + await expect( + db + .update(knowledgeConnectorPartition) + .set({ context: { payload } }) + .where(eq(knowledgeConnectorPartition.connectorId, owner.connectorId)) + ).rejects.toMatchObject({ cause: { code: '23514', constraint_name: 'kcp_context_check' } }) + } + }) + + it.each([ + ['empty', ''], + ['oversized ASCII', 'a'.repeat(1025)], + ['oversized multibyte', 'あ'.repeat(342)], + ])('rejects %s partition keys', async (_label, partitionKey) => { + await expect( + db + .update(knowledgeConnectorPartition) + .set({ partitionKey }) + .where( + and( + eq(knowledgeConnectorPartition.connectorId, owner.connectorId), + eq(knowledgeConnectorPartition.partitionKey, first.partitionKey) + ) + ) + ).rejects.toMatchObject({ + cause: { code: '23514', constraint_name: 'kcp_partition_key_check' }, + }) + }) + + it('accepts a partition key at the byte limit', async () => { + const partitionKey = 'a'.repeat(1024) + await db + .update(knowledgeConnectorPartition) + .set({ partitionKey }) + .where( + and( + eq(knowledgeConnectorPartition.connectorId, owner.connectorId), + eq(knowledgeConnectorPartition.partitionKey, first.partitionKey) + ) + ) + const store = connectorPartitionWorkStore(owner.connectorId, generationId, parseContext) + expect((await store.get(partitionKey, 'content'))?.context).toEqual(first.context) + }) + + it('rolls back partition progress and the owning checkpoint together', async () => { + const store = connectorPartitionWorkStore(owner.connectorId, generationId, parseContext) + const before = await store.get('first', 'content') + await expect( + db.transaction(async (tx) => { + await assertSyncLeaseHeldInTx(tx, owner.connectorId, { + stillHeld: () => stillHoldsSyncLock(owner.connectorId, owner.lockId), + }) + await commitConnectorPartitionWork( + tx, + owner.connectorId, + generationId, + { + update: { + partitionKey: 'first', + kind: 'content', + cursor: 'next-page-92', + completed: false, + retryAt: new Date(), + attempts: 0, + failure: null, + }, + }, + permissionRefreshAt + ) + await tx + .update(knowledgeConnector) + .set({ listingCheckpoint: { ...checkpoint, cursor: 'outer-next-page' } }) + .where(eq(knowledgeConnector.id, owner.connectorId)) + throw new Error('simulated worker transaction failure') + }) + ).rejects.toThrow('simulated worker transaction failure') + expect(await store.get('first', 'content')).toEqual(before) + const [connector] = await db + .select({ checkpoint: knowledgeConnector.listingCheckpoint }) + .from(knowledgeConnector) + .where(eq(knowledgeConnector.id, owner.connectorId)) + expect(connector.checkpoint).toEqual(checkpoint) + }) + + it('pins provider replay, preserves it during repeated partition discovery and refreshes old evidence promptly', async () => { + await db.transaction(async (tx) => { + await commitConnectorPartitionWork( + tx, + owner.connectorId, + generationId, + { pin: { partitionKey: 'first', kind: 'content', cursor: 'stable-listing-snapshot' } }, + permissionRefreshAt + ) + await commitConnectorPartitionWork( + tx, + owner.connectorId, + generationId, + { enqueue: [{ ...first, cursor: 'must-not-overwrite' }] }, + permissionRefreshAt + ) + await commitConnectorPartitionWork( + tx, + owner.connectorId, + generationId, + { + update: { + partitionKey: 'first', + kind: 'content', + cursor: 'next-content-page', + completed: false, + retryAt: new Date(), + attempts: 0, + failure: null, + }, + }, + permissionRefreshAt + ) + }) + const store = connectorPartitionWorkStore(owner.connectorId, generationId, parseContext) + expect((await store.get('first', 'content'))?.cursor).toBe('next-content-page') + expect((await store.next('content', new Date()))?.partitionKey).toBe('second') + expect((await store.next('permissions', new Date()))?.partitionKey).toBe('first') + expect(await store.get('first', 'permissions')).toMatchObject({ + cursor: undefined, + attempts: 0, + }) + }) + + it('retains blocked work independently of healthy completions and excludes another generation', async () => { + await db.transaction(async (tx) => { + await commitConnectorPartitionWork( + tx, + owner.connectorId, + generationId, + { + update: { + partitionKey: 'first', + kind: 'content', + cursor: 'next-content-page', + completed: false, + retryAt: new Date(Date.now() + 60 * 60 * 1000), + attempts: 1, + failure: { + scope: first.context.spaceKey, + operation: 'confluence.pages.list', + status: 403, + reasons: [], + }, + }, + }, + permissionRefreshAt + ) + await commitConnectorPartitionWork( + tx, + owner.connectorId, + generationId, + { + update: { + partitionKey: 'second', + kind: 'content', + cursor: null, + completed: true, + retryAt: new Date(Date.now() + 60 * 60 * 1000), + attempts: 0, + failure: null, + }, + }, + permissionRefreshAt + ) + await commitConnectorPartitionWork( + tx, + owner.connectorId, + 'different-generation', + { + enqueue: [ + { partitionKey: 'other', context: { spaceKey: 'OTHER', siteId: 'fixture-site' } }, + ], + }, + permissionRefreshAt + ) + }) + const store = connectorPartitionWorkStore(owner.connectorId, generationId, parseContext) + expect(await store.next('content', new Date())).toBeNull() + expect(await store.remaining()).toMatchObject({ + count: 1, + failures: { count: 1, samples: [{ scope: first.context.spaceKey, status: 403 }] }, + }) + expect((await store.get('first', 'content'))?.cursor).toBe('next-content-page') + expect(await store.get('other', 'content')).toBeNull() + expect( + await connectorPartitionWorkStore('other-connector', generationId, parseContext).remaining() + ).toEqual({ + count: 0, + retryAt: null, + }) + }) + + it('does not rescan completed manual work or use its past retry time to wake blocked work', async () => { + await commitConnectorPartitionWork( + db, + owner.connectorId, + generationId, + { + update: { + partitionKey: 'first', + kind: 'content', + cursor: 'blocked-partition-page', + completed: false, + retryAt: new Date(Date.now() + 60 * 60_000), + attempts: 1, + failure: { + scope: first.context.spaceKey, + operation: 'confluence.pages.list', + reasons: [], + }, + }, + }, + permissionRefreshAt + ) + await commitConnectorPartitionWork( + db, + owner.connectorId, + generationId, + { + update: { + partitionKey: 'second', + kind: 'content', + cursor: null, + completed: true, + retryAt: new Date(Date.now() - 60_000), + attempts: 0, + failure: null, + }, + }, + permissionRefreshAt + ) + const automatic = connectorPartitionWorkStore(owner.connectorId, generationId, parseContext) + const manual = connectorPartitionWorkStore(owner.connectorId, generationId, parseContext, false) + expect((await automatic.next('content', new Date()))?.partitionKey).toBe('second') + expect(await manual.next('content', new Date())).toBeNull() + expect((await manual.remaining()).retryAt!.getTime()).toBeGreaterThan(Date.now()) + expect((await manual.next('permissions', new Date()))?.partitionKey).toBe('first') + await commitConnectorPartitionWork( + db, + owner.connectorId, + generationId, + { + update: { + partitionKey: 'first', + kind: 'content', + cursor: null, + completed: true, + retryAt: new Date(Date.now() - 60_000), + attempts: 0, + failure: null, + }, + }, + permissionRefreshAt + ) + expect(await automatic.next('content', new Date())).toBeNull() + expect((await automatic.remaining()).count).toBe(0) + }) + + it.each(['continuation', 'failure'] as const)( + 'retains a deferred permission %s after all content completes without keeping future refreshes open', + async (pending) => { + vi.stubEnv('TZ', 'Pacific/Honolulu') + const now = new Date() + const retryAt = new Date(now.getTime() + 30 * 60_000) + const nextPeriodicRefresh = new Date(now.getTime() + 12 * 60 * 60_000) + const update = ( + partitionKey: string, + kind: ConnectorPartitionWorkUpdate['kind'], + changes: Partial = {} + ) => + commitConnectorPartitionWork( + db, + owner.connectorId, + generationId, + { + update: { + partitionKey, + kind, + cursor: null, + completed: true, + retryAt: nextPeriodicRefresh, + attempts: 0, + failure: null, + permissionStartedAt: null, + ...changes, + }, + }, + permissionRefreshAt + ) + for (const partitionKey of ['first', 'second']) { + await update(partitionKey, 'permissions') + await update(partitionKey, 'content', { retryAt: new Date(now.getTime() - 60_000) }) + } + await update('first', 'permissions', { + cursor: pending === 'continuation' ? 'permission-page-9' : null, + completed: false, + retryAt, + attempts: pending === 'failure' ? 1 : 0, + failure: + pending === 'failure' + ? { + scope: first.context.spaceKey, + operation: 'confluence.pages.list', + status: 403, + reasons: [], + } + : null, + }) + const automatic = connectorPartitionWorkStore(owner.connectorId, generationId, parseContext) + const manual = connectorPartitionWorkStore( + owner.connectorId, + generationId, + parseContext, + false + ) + expect(await manual.remaining()).toMatchObject({ count: 1, retryAt }) + expect(await manual.next('content', now)).toBeNull() + expect(await automatic.next('content', now)).not.toBeNull() + expect(await automatic.next('permissions', now)).toBeNull() + expect((await automatic.next('permissions', retryAt))?.partitionKey).toBe('first') + for (const partitionKey of ['first', 'second']) { + await update(partitionKey, 'content', { + retryAt: new Date(now.getTime() + 2 * 60 * 60_000), + }) + } + expect(await automatic.remaining()).toMatchObject({ count: 1, retryAt }) + await update('first', 'permissions') + expect(await automatic.remaining()).toEqual({ count: 0, retryAt: null }) + expect(await manual.remaining()).toEqual({ count: 0, retryAt: null }) + expect(await automatic.next('content', new Date(now.getTime() + 3 * 60 * 60_000))).toBeNull() + } + ) + + it('resets both provider continuations while preserving due permissions, and cascades on connector deletion', async () => { + const permissionDueAt = new Date(Date.now() - 60_000) + await db + .update(knowledgeConnectorPartition) + .set({ permissionRetryAt: permissionDueAt }) + .where(eq(knowledgeConnectorPartition.connectorId, owner.connectorId)) + await db.transaction(async (tx) => { + await commitConnectorPartitionWork( + tx, + owner.connectorId, + generationId, + { + pin: { + partitionKey: 'first', + kind: 'permissions', + cursor: 'old-config-permission-page', + permissionStartedAt: generationStartedAt, + }, + }, + permissionRefreshAt + ) + await commitConnectorPartitionWork( + tx, + owner.connectorId, + 'new-config', + { enqueue: [{ ...first, cursor: 'new-config-first-page' }] }, + new Date(Date.now() + 12 * 60 * 60_000) + ) + }) + const store = connectorPartitionWorkStore(owner.connectorId, 'new-config', parseContext) + expect(await store.get('first', 'content')).toMatchObject({ + cursor: 'new-config-first-page', + attempts: 0, + }) + expect(await store.get('first', 'permissions')).toMatchObject({ + cursor: undefined, + permissionStartedAt: undefined, + }) + const [progress] = await db + .select({ permissionRetryAt: knowledgeConnectorPartition.permissionRetryAt }) + .from(knowledgeConnectorPartition) + .where( + and( + eq(knowledgeConnectorPartition.connectorId, owner.connectorId), + eq(knowledgeConnectorPartition.partitionKey, 'first') + ) + ) + expect(progress.permissionRetryAt).toEqual(permissionDueAt) + await db.delete(knowledgeConnector).where(eq(knowledgeConnector.id, owner.connectorId)) + expect( + await db + .select() + .from(knowledgeConnectorPartition) + .where(eq(knowledgeConnectorPartition.connectorId, owner.connectorId)) + ).toEqual([]) + }) +}) diff --git a/apps/sim/lib/knowledge/__integration__/listing-continuation.integration.ts b/apps/sim/lib/knowledge/__integration__/listing-continuation.integration.ts index cf87bd54aa7..c24e2476960 100644 --- a/apps/sim/lib/knowledge/__integration__/listing-continuation.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/listing-continuation.integration.ts @@ -12,6 +12,7 @@ import { embedding, knowledgeConnector, knowledgeConnectorMember, + knowledgeConnectorPartition, knowledgeConnectorSyncLog, knowledgeDocumentObservation, resourcePolicy, @@ -22,7 +23,16 @@ import { generateId } from '@sim/utils/id' import { and, eq, inArray, sql } from 'drizzle-orm' import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' -const fixture = vi.hoisted(() => ({ storageRoot: '', list: vi.fn(), get: vi.fn() })) +const fixture = vi.hoisted(() => ({ + storageRoot: '', + list: vi.fn(), + get: vi.fn(), + directory: vi.fn(), +})) +vi.mock('@/connectors/google-workspace/users', async (original) => ({ + ...(await original()), + listGoogleWorkspaceUsers: fixture.directory, +})) vi.mock('@/lib/uploads/core/setup.server', () => ({ get UPLOAD_DIR_SERVER() { return fixture.storageRoot @@ -69,7 +79,10 @@ import { import { searchKnowledge } from '@/lib/knowledge/application/search' import * as connectorTokens from '@/lib/knowledge/connectors/access-token' import { getConnectorFailureDiagnostic } from '@/lib/knowledge/connectors/connector-error' -import { listingFingerprint } from '@/lib/knowledge/connectors/listing-checkpoint' +import { + beginListingCheckpoint, + listingFingerprint, +} from '@/lib/knowledge/connectors/listing-checkpoint' import * as memberAccess from '@/lib/knowledge/connectors/member-access' import { materializeDocumentAcls, @@ -81,13 +94,16 @@ import { } from '@/lib/knowledge/connectors/member-sync-engine' import { runConnectorContentPass } from '@/lib/knowledge/connectors/sync-content-pass' import { executeSync } from '@/lib/knowledge/connectors/sync-engine' -import { SOURCE_CONTENT_ERROR } from '@/lib/knowledge/connectors/sync-limits' +import { + SOURCE_CONTENT_ERROR, + SOURCE_PERMISSION_ERROR, +} from '@/lib/knowledge/connectors/sync-limits' import { createContentSyncLease, createMemberSyncLease } from '@/lib/knowledge/connectors/sync-lock' -import { addDocument } from '@/lib/knowledge/connectors/sync-persistence' +import { addDocument, persistDocumentAcls } from '@/lib/knowledge/connectors/sync-persistence' import * as documentService from '@/lib/knowledge/documents/service' import { downloadFileFromUrl } from '@/lib/uploads/utils/file-utils.server' import { CONNECTOR_REGISTRY } from '@/connectors/registry.server' -import type { ExternalDocument, SyncResult } from '@/connectors/types' +import type { ConnectorConfig, ExternalDocument, SyncResult } from '@/connectors/types' const body = 'Durable source pagination preserves the checkpoint, indexes each document once, and applies access revocation only after an authoritative complete listing.' @@ -150,6 +166,487 @@ describe('durable source and member cycles in PostgreSQL', () => { }) } + it('refreshes verified existing permissions while repairing changed bodies, without indexing discoveries or reconciling absence', async () => { + const connectorId = generateId() + const runId = generateId() + await db.insert(knowledgeConnector).values({ + id: connectorId, + knowledgeBaseId: ids.knowledgeBaseId, + connectorType: 'google_drive', + status: 'syncing', + syncLockToken: runId, + accessMode: 'admin', + sourceConfig: {}, + }) + const oldVerifiedAt = new Date(Date.now() - 25 * 60 * 60 * 1000) + const ownerAcl = [`u:${ids.aliceId}@fixture.test`] + const stored = ['unchanged', 'changed', 'failed', 'absent'].map((id) => ({ + id: generateId(), + knowledgeBaseId: ids.knowledgeBaseId, + connectorId, + externalId: `refresh-${id}`, + filename: id, + fileUrl: '', + storageKey: `kb/refresh-old-${id}.txt`, + fileSize: 10, + mimeType: 'text/plain', + contentHash: id === 'unchanged' ? 'hash-refresh-unchanged' : 'old-body', + processingStatus: 'completed', + acl: ownerAcl, + aclVerifiedAt: oldVerifiedAt, + sourceSeenAt: oldVerifiedAt, + })) + await db.insert(document).values(stored) + const documents = ['unchanged', 'changed', 'failed', 'new'].map((id) => ({ + ...sourceDoc(`refresh-${id}`), + content: '', + contentDeferred: true, + acl: ownerAcl, + })) + const listDocuments = vi.fn(async () => ({ + documents, + hasMore: false, + permissionsOnly: true, + reconciliationSafe: false, + })) + const getDocument = vi.fn(async (externalId: string) => { + if (externalId === 'refresh-failed') throw new Error('User document unavailable') + const [duringHydration] = await db + .select({ acl: document.acl }) + .from(document) + .where(and(eq(document.connectorId, connectorId), eq(document.externalId, externalId))) + expect(duringHydration.acl).toEqual([]) + return sourceDoc(externalId) + }) + const [connector] = await db + .select() + .from(knowledgeConnector) + .where(eq(knowledgeConnector.id, connectorId)) + const syncResult = result() + const lease = createContentSyncLease(connectorId, runId) + const pass = await runConnectorContentPass({ + connectorId, + connector, + connectorConfig: { ...CONNECTOR_REGISTRY.google_drive, listDocuments }, + sourceConfig: {}, + syncContext: {}, + kbOwner: { userId: ids.aliceId, workspaceId: ids.workspaceId }, + billingAttribution: billing, + result: syncResult, + lease, + leaseKind: 'content', + runId, + fingerprint: listingFingerprint({ source: 'permission-refresh' }), + documentAccess: 'admin', + getAccessToken: async () => 'fixture', + hydration: { getDocument }, + forceRehydrate: false, + deadlineAt: Date.now() + 60_000, + onPage: async (verified) => { + const write = await persistDocumentAcls( + connectorId, + new Map(verified.map((item) => [item.externalId, item.acl!])) + ) + return { permissionsIncomplete: write.rejected > 0 } + }, + }) + expect(getDocument.mock.calls.map(([id]) => id).sort()).toEqual([ + 'refresh-changed', + 'refresh-failed', + ]) + const rows = await db.select().from(document).where(eq(document.connectorId, connectorId)) + expect(rows).toHaveLength(4) + expect(rows.every((row) => row.deletedAt === null)).toBe(true) + const unchanged = rows.find((row) => row.externalId === 'refresh-unchanged')! + expect(unchanged.aclVerifiedAt!.getTime()).toBeGreaterThan(oldVerifiedAt.getTime()) + expect(unchanged.sourceSeenAt).toEqual(oldVerifiedAt) + const changed = rows.find((row) => row.externalId === 'refresh-changed')! + expect(changed.contentHash).toBe('hash-refresh-changed') + expect(changed.acl).toEqual(ownerAcl) + expect(changed.aclVerifiedAt!.getTime()).toBeGreaterThan(oldVerifiedAt.getTime()) + expect(changed.sourceSeenAt).toEqual(oldVerifiedAt) + const failed = rows.find((row) => row.externalId === 'refresh-failed')! + expect(failed.acl).toEqual([]) + expect(failed.aclVerifiedAt).toBeNull() + expect(failed.processingStatus).toBe('failed') + expect(failed.sourceSeenAt).toEqual(oldVerifiedAt) + expect(rows.find((row) => row.externalId === 'refresh-absent')!.aclVerifiedAt).toEqual( + oldVerifiedAt + ) + expect(pass.checkpoint.listedCount).toBe(0) + expect(syncResult).toMatchObject({ + docsAdded: 0, + docsUpdated: 1, + docsFailed: 1, + docsDeleted: 0, + }) + }) + + it.each([ + { description: 'a matching-hash body placeholder', storageKey: null, rejectAcl: false }, + { description: 'an unusable source ACL', storageKey: 'kb/verified-body.txt', rejectAcl: true }, + ])('clears stale permission evidence for $description', async ({ storageKey, rejectAcl }) => { + const connectorId = generateId() + const runId = generateId() + const externalId = 'permission-evidence' + const oldVerifiedAt = new Date(Date.now() - 25 * 60 * 60 * 1000) + const ownerAcl = [`u:${ids.aliceId}@fixture.test`] + await db.insert(knowledgeConnector).values({ + id: connectorId, + knowledgeBaseId: ids.knowledgeBaseId, + connectorType: 'google_drive', + status: 'syncing', + syncLockToken: runId, + accessMode: 'admin', + sourceConfig: {}, + }) + await db.insert(document).values({ + id: generateId(), + knowledgeBaseId: ids.knowledgeBaseId, + connectorId, + externalId, + filename: externalId, + fileUrl: '', + storageKey, + fileSize: storageKey === null ? 0 : 10, + mimeType: 'text/plain', + contentHash: `hash-${externalId}`, + processingStatus: storageKey === null ? 'failed' : 'completed', + processingError: storageKey === null ? 'Unsupported source file type' : null, + acl: ownerAcl, + aclRequirements: [ownerAcl, ['d:fixture.test']], + aclVerifiedAt: oldVerifiedAt, + sourceSeenAt: oldVerifiedAt, + }) + const listDocuments = vi.fn(async () => ({ + documents: [ + { + ...sourceDoc(externalId), + content: '', + contentDeferred: true, + acl: rejectAcl ? ['invalid-principal'] : ownerAcl, + }, + ], + hasMore: false, + permissionsOnly: true, + reconciliationSafe: false, + })) + const getDocument = vi.fn(async () => sourceDoc(externalId)) + const [connector] = await db + .select() + .from(knowledgeConnector) + .where(eq(knowledgeConnector.id, connectorId)) + const pass = await runConnectorContentPass({ + connectorId, + connector, + connectorConfig: { ...CONNECTOR_REGISTRY.google_drive, listDocuments }, + sourceConfig: {}, + syncContext: {}, + kbOwner: { userId: ids.aliceId, workspaceId: ids.workspaceId }, + billingAttribution: billing, + result: result(), + lease: createContentSyncLease(connectorId, runId), + leaseKind: 'content', + runId, + fingerprint: listingFingerprint({ source: 'permission-evidence' }), + documentAccess: 'admin', + getAccessToken: async () => 'fixture', + hydration: { getDocument }, + forceRehydrate: false, + deadlineAt: Date.now() + 60_000, + onPage: async (verified) => { + const write = await persistDocumentAcls( + connectorId, + new Map(verified.map((item) => [item.externalId, item.acl!])) + ) + return { permissionsIncomplete: write.rejected > 0 } + }, + }) + const [stored] = await db.select().from(document).where(eq(document.connectorId, connectorId)) + expect(stored).toMatchObject({ + acl: [], + aclRequirements: [], + aclVerifiedAt: null, + sourceSeenAt: oldVerifiedAt, + contentHash: `hash-${externalId}`, + storageKey, + deletedAt: null, + }) + expect(getDocument).not.toHaveBeenCalled() + expect(pass.checkpoint.permissionFailures).toBe(rejectAcl) + if (rejectAcl) expect(pass.holdNotice).toBe(SOURCE_PERMISSION_ERROR) + }) + + it('preserves a permission-repaired document through the remaining content crawl and EOF reconciliation', async () => { + const connectorId = generateId() + const runId = generateId() + const fingerprint = listingFingerprint({ source: 'permission-refresh-before-eof' }) + const checkpoint = { + ...beginListingCheckpoint({ + fingerprint, + generationId: generateId(), + startedAt: new Date(Date.now() - 13 * 60 * 60 * 1000), + }), + cursor: 'permissions', + listedCount: 1, + } + const seenAt = new Date(checkpoint.startedAt) + const oldVerifiedAt = new Date(seenAt.getTime() - 60 * 60 * 1000) + const ownerAcl = [`u:${ids.aliceId}@fixture.test`] + await db.insert(knowledgeConnector).values({ + id: connectorId, + knowledgeBaseId: ids.knowledgeBaseId, + connectorType: 'google_drive', + status: 'syncing', + syncLockToken: runId, + accessMode: 'admin', + sourceConfig: {}, + listingCheckpoint: checkpoint, + }) + await db.insert(document).values( + ['repaired', 'remaining', 'absent'].map((name) => ({ + id: generateId(), + knowledgeBaseId: ids.knowledgeBaseId, + connectorId, + externalId: `permission-eof-${name}`, + filename: name, + fileUrl: '', + storageKey: `kb/permission-eof-${name}.txt`, + fileSize: 10, + mimeType: 'text/plain', + contentHash: name === 'repaired' ? 'old-body' : `hash-permission-eof-${name}`, + processingStatus: 'completed', + acl: ownerAcl, + aclVerifiedAt: oldVerifiedAt, + sourceSeenAt: name === 'repaired' ? seenAt : oldVerifiedAt, + })) + ) + const listDocuments = vi.fn( + async (_token, _source, cursor) => + cursor === 'permissions' + ? { + documents: [ + { + ...sourceDoc('permission-eof-repaired'), + content: '', + contentDeferred: true, + acl: ownerAcl, + }, + ], + hasMore: true, + nextCursor: 'remaining', + permissionsOnly: true, + resumeAt: new Date(Date.now() + 60_000).toISOString(), + } + : { + documents: [{ ...sourceDoc('permission-eof-remaining'), acl: ownerAcl }], + hasMore: false, + } + ) + const getDocument = vi.fn(async (externalId: string) => { + const [duringHydration] = await db + .select({ acl: document.acl, aclVerifiedAt: document.aclVerifiedAt }) + .from(document) + .where(and(eq(document.connectorId, connectorId), eq(document.externalId, externalId))) + expect(duringHydration).toEqual({ acl: [], aclVerifiedAt: null }) + return sourceDoc(externalId) + }) + const run = async (leaseId: string) => { + const [connector] = await db + .select() + .from(knowledgeConnector) + .where(eq(knowledgeConnector.id, connectorId)) + const stats = result() + const pass = await runConnectorContentPass({ + connectorId, + connector, + connectorConfig: { ...CONNECTOR_REGISTRY.google_drive, listDocuments }, + sourceConfig: {}, + syncContext: {}, + kbOwner: { userId: ids.aliceId, workspaceId: ids.workspaceId }, + billingAttribution: billing, + result: stats, + lease: createContentSyncLease(connectorId, leaseId), + leaseKind: 'content', + runId: leaseId, + fingerprint, + documentAccess: 'admin', + getAccessToken: async () => 'fixture', + hydration: { getDocument }, + forceRehydrate: false, + deadlineAt: Date.now() + 60_000, + onPage: async (verified) => { + const write = await persistDocumentAcls( + connectorId, + new Map(verified.map((item) => [item.externalId, item.acl!])) + ) + return { permissionsIncomplete: write.rejected > 0 } + }, + }) + return { pass, stats } + } + const first = await run(runId) + expect(first.pass).toMatchObject({ + complete: false, + checkpoint: { cursor: 'remaining', listedCount: 1, unsafe: false }, + }) + expect(first.stats).toMatchObject({ docsUpdated: 1, docsDeleted: 0 }) + const nextLease = generateId() + await db + .update(knowledgeConnector) + .set({ syncLockToken: nextLease }) + .where(eq(knowledgeConnector.id, connectorId)) + const final = await run(nextLease) + expect(final.pass).toMatchObject({ + complete: true, + holdNotice: null, + checkpoint: { generationId: checkpoint.generationId, listedCount: 2, unsafe: false }, + }) + expect(getDocument).toHaveBeenCalledOnce() + expect(listDocuments.mock.calls.map((call) => call[2])).toEqual(['permissions', 'remaining']) + const rows = await db.select().from(document).where(eq(document.connectorId, connectorId)) + const repaired = rows.find((row) => row.externalId === 'permission-eof-repaired')! + expect(repaired).toMatchObject({ + contentHash: 'hash-permission-eof-repaired', + processingStatus: 'completed', + acl: ownerAcl, + sourceSeenAt: seenAt, + deletedAt: null, + }) + expect(repaired.aclVerifiedAt!.getTime()).toBeGreaterThan(oldVerifiedAt.getTime()) + expect(rows.find((row) => row.externalId === 'permission-eof-absent')).toMatchObject({ + acl: [], + deletedAt: expect.any(Date), + }) + expect(final.stats).toMatchObject({ docsUnchanged: 1, docsDeleted: 1 }) + }) + + it('repairs a changed Google document already seen in an unfinished company generation', async () => { + const connectorId = generateId() + const runId = generateId() + const fingerprint = listingFingerprint({ source: 'google-content-rescan' }) + const checkpoint = { + ...beginListingCheckpoint({ + fingerprint, + generationId: 'rescan-generation', + startedAt: new Date(Date.now() - 13 * 60 * 60 * 1000), + }), + cursor: `google-company-work:v2:${Buffer.from( + JSON.stringify({ + directoryComplete: true, + directoryRefreshAt: new Date(Date.now() + 60 * 60 * 1000).toISOString(), + phase: 'users', + revision: 5, + permissionTurn: false, + active: { userId: 'google-user', kind: 'content' }, + }) + ).toString('base64url')}`, + listedCount: 1, + } + await db.insert(knowledgeConnector).values({ + id: connectorId, + knowledgeBaseId: ids.knowledgeBaseId, + connectorType: 'google_drive', + status: 'syncing', + syncLockToken: runId, + accessMode: 'admin', + sourceConfig: {}, + listingCheckpoint: checkpoint, + }) + await db.insert(document).values({ + id: generateId(), + knowledgeBaseId: ids.knowledgeBaseId, + connectorId, + externalId: 'rescan-changed', + filename: 'Rescan', + fileUrl: '', + storageKey: 'kb/rescan-old.txt', + fileSize: 10, + mimeType: 'text/plain', + contentHash: 'old-body', + sourceSeenAt: new Date(checkpoint.startedAt), + processingStatus: 'completed', + acl: [], + }) + await db.insert(knowledgeConnectorPartition).values({ + connectorId, + generationId: checkpoint.generationId, + partitionKey: 'google-user', + context: { id: 'google-user', email: 'google-user@fixture.test', customerId: 'customer' }, + cursor: 'saved-content-page-91', + lastServedAt: new Date(checkpoint.startedAt), + permissionRetryAt: new Date(Date.now() + 12 * 60 * 60 * 1000), + }) + fixture.directory.mockClear() + const listed = { + ...sourceDoc('rescan-changed'), + content: '', + contentDeferred: true, + acl: ['u:google-user@fixture.test'], + } + const listDocuments = vi.fn( + async (_token, _source, cursor) => { + expect(cursor).toBe('saved-content-page-91') + return { documents: [listed], hasMore: false } + } + ) + const getDocument = vi.fn(async () => sourceDoc('rescan-changed')) + const [connector] = await db + .select() + .from(knowledgeConnector) + .where(eq(knowledgeConnector.id, connectorId)) + const syncResult = result() + const pass = await runConnectorContentPass({ + connectorId, + connector, + connectorConfig: { ...CONNECTOR_REGISTRY.google_drive, listDocuments }, + sourceConfig: {}, + syncContext: { + mirrorsSourceAcls: true, + getDelegatedAccessToken: async () => 'fixture-user-token', + }, + kbOwner: { userId: ids.aliceId, workspaceId: ids.workspaceId }, + billingAttribution: billing, + result: syncResult, + lease: createContentSyncLease(connectorId, runId), + leaseKind: 'content', + runId, + fingerprint, + documentAccess: 'admin', + getAccessToken: async () => 'fixture-directory-token', + hydration: { getDocument }, + forceRehydrate: false, + deadlineAt: Date.now() + 60_000, + onPage: async (verified) => { + const write = await persistDocumentAcls( + connectorId, + new Map(verified.map((item) => [item.externalId, item.acl!])) + ) + return { permissionsIncomplete: write.rejected > 0 } + }, + }) + expect(pass.complete).toBe(true) + expect(pass.checkpoint.generationId).toBe(checkpoint.generationId) + expect(fixture.directory).not.toHaveBeenCalled() + expect(listDocuments).toHaveBeenCalledOnce() + expect(getDocument).toHaveBeenCalledOnce() + expect(syncResult.docsUpdated).toBe(1) + const [userProgress] = await db + .select() + .from(knowledgeConnectorPartition) + .where(eq(knowledgeConnectorPartition.connectorId, connectorId)) + expect(userProgress).toMatchObject({ + generationId: checkpoint.generationId, + cursor: null, + status: 'complete', + }) + const [stored] = await db.select().from(document).where(eq(document.connectorId, connectorId)) + expect(stored.contentHash).toBe('hash-rescan-changed') + expect(stored.aclVerifiedAt!.getTime()).toBeGreaterThan( + new Date(checkpoint.startedAt).getTime() + ) + }) + it('reconciles NULL and microsecond timestamp ties across ACL, soft-delete, and hard-delete pages', async () => { const connectorId = generateId() let runId = generateId() diff --git a/apps/sim/lib/knowledge/__integration__/stored-document-recovery.integration.ts b/apps/sim/lib/knowledge/__integration__/stored-document-recovery.integration.ts index 80c4fcb7172..8d3a3f45a09 100644 --- a/apps/sim/lib/knowledge/__integration__/stored-document-recovery.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/stored-document-recovery.integration.ts @@ -6,6 +6,7 @@ import path from 'node:path' import { db } from '@sim/db' import { document, + embedding, knowledgeBase, knowledgeConnector, member, @@ -340,8 +341,8 @@ describe('independent recovery of retained connector documents', () => { return resolve(id) }) try { - expect(await recoverKnowledgeDocumentProcessing()).toBe(0) expect(await recoverKnowledgeDocumentProcessing()).toBe(1) + expect(await recoverKnowledgeDocumentProcessing()).toBe(0) } finally { spy.mockRestore() } @@ -384,6 +385,176 @@ describe('independent recovery of retained connector documents', () => { } ) + it('recovers a sibling connector when the oldest candidate batch belongs to a locked connector', async () => { + const blocked = await seed() + const file = await failedFile(blocked) + const [original] = await db.select().from(document).where(eq(document.id, file.documentId)) + await db.insert(document).values( + Array.from({ length: DOCUMENT_RECOVERY_BATCH_SIZE - 1 }, () => ({ + ...original, + id: generateId(), + externalId: generateId(), + secretProvenanceVersion: null, + })) + ) + const healthy = { ...blocked, connectorId: generateId(), lockId: generateId() } + await db.insert(knowledgeConnector).values({ + id: healthy.connectorId, + knowledgeBaseId: healthy.knowledgeBaseId, + connectorType: 'google_drive', + sourceConfig: {}, + accessMode: 'admin', + status: 'syncing', + syncLockToken: healthy.lockId, + }) + const healthyFile = await failedFile(healthy) + await db + .update(document) + .set({ uploadedAt: new Date(original.uploadedAt.getTime() + 1) }) + .where(eq(document.id, healthyFile.documentId)) + + let release!: () => void + let acquired!: () => void + const released = new Promise((resolve) => { + release = resolve + }) + const locked = new Promise((resolve) => { + acquired = resolve + }) + const holder = db.transaction(async (tx) => { + await tx + .select({ id: knowledgeBase.id }) + .from(knowledgeBase) + .where(eq(knowledgeBase.id, blocked.knowledgeBaseId)) + .for('share') + await tx + .select({ id: knowledgeConnector.id }) + .from(knowledgeConnector) + .where(eq(knowledgeConnector.id, blocked.connectorId)) + .for('update') + acquired() + await released + }) + await locked + try { + expect(await recoverKnowledgeDocumentProcessing()).toBe(1) + const events = await eventsFor(blocked) + expect(events).toHaveLength(1) + expect(events[0].payload).toMatchObject({ documentId: healthyFile.documentId }) + const [admitted] = await db + .select() + .from(document) + .where(eq(document.id, healthyFile.documentId)) + expect(admitted.processingAttempts).toBe(2) + expect(admitted.processingQueueToken).toBe(events[0].id) + const untouched = await db + .select() + .from(document) + .where(eq(document.connectorId, blocked.connectorId)) + expect(untouched).toHaveLength(DOCUMENT_RECOVERY_BATCH_SIZE) + for (const row of untouched) { + expect(row).toMatchObject({ + processingStatus: original.processingStatus, + processingAttempts: original.processingAttempts, + processingQueueToken: original.processingQueueToken, + processingQueuedAt: original.processingQueuedAt, + processingError: original.processingError, + processingRecoveryAfter: original.processingRecoveryAfter, + }) + } + } finally { + release() + await holder + await db + .update(knowledgeConnector) + .set({ status: 'paused' }) + .where(inArray(knowledgeConnector.id, [blocked.connectorId, healthy.connectorId])) + } + }) + + it('recovers another document while an index transaction holds the same KB foreign-key lock', async () => { + const ids = await seed() + const file = await failedFile(ids) + const indexedId = generateId() + await db.insert(document).values({ + id: indexedId, + knowledgeBaseId: ids.knowledgeBaseId, + filename: 'Concurrent index.txt', + fileUrl: 'data:text/plain,fixture', + fileSize: 7, + mimeType: 'text/plain', + processingStatus: 'completed', + }) + let release!: () => void + let acquired!: () => void + const released = new Promise((resolve) => { + release = resolve + }) + const locked = new Promise((resolve) => { + acquired = resolve + }) + const indexing = db.transaction(async (tx) => { + await tx.insert(embedding).values({ + id: generateId(), + knowledgeBaseId: ids.knowledgeBaseId, + documentId: indexedId, + chunkIndex: 0, + chunkHash: 'fixture', + content: 'fixture', + contentLength: 7, + tokenCount: 1, + startOffset: 0, + endOffset: 7, + embedding: [1, ...Array(1535).fill(0)], + }) + acquired() + await released + }) + await locked + try { + expect(await recoverKnowledgeDocumentProcessing()).toBe(1) + expect(await eventsFor(ids)).toHaveLength(1) + const [admitted] = await db.select().from(document).where(eq(document.id, file.documentId)) + expect(admitted.processingAttempts).toBe(2) + } finally { + release() + await indexing + } + }) + + it('does not claim work while a KB soft deletion is committing', async () => { + const ids = await seed() + const file = await failedFile(ids) + let release!: () => void + let acquired!: () => void + const released = new Promise((resolve) => { + release = resolve + }) + const locked = new Promise((resolve) => { + acquired = resolve + }) + const deletion = db.transaction(async (tx) => { + await tx + .update(knowledgeBase) + .set({ deletedAt: new Date() }) + .where(eq(knowledgeBase.id, ids.knowledgeBaseId)) + acquired() + await released + }) + await locked + try { + expect(await recoverKnowledgeDocumentProcessing()).toBe(0) + } finally { + release() + await deletion + } + expect(await recoverKnowledgeDocumentProcessing()).toBe(0) + expect(await eventsFor(ids)).toHaveLength(0) + const [original] = await db.select().from(document).where(eq(document.id, file.documentId)) + expect(original.processingAttempts).toBe(1) + expect(original.processingQueueToken).toBe('old-fixture-generation') + }) + it('replays the additive migration and leaves the concurrent recovery index valid', async () => { const migration = readFileSync( path.resolve( diff --git a/apps/sim/lib/knowledge/application/connectors.ts b/apps/sim/lib/knowledge/application/connectors.ts index dee4bb40742..6f809bdde88 100644 --- a/apps/sim/lib/knowledge/application/connectors.ts +++ b/apps/sim/lib/knowledge/application/connectors.ts @@ -370,8 +370,10 @@ async function resolveConnectorValidationAccessToken( if (!resolved || !getDelegatedAccessToken) return resolved return { ...resolved, - getDelegatedAccessToken: (subject) => - getDelegatedAccessToken(subject).catch(rethrowConnectorCredentialError), + getDelegatedAccessToken: (subject, signal) => + (signal ? getDelegatedAccessToken(subject, signal) : getDelegatedAccessToken(subject)).catch( + rethrowConnectorCredentialError + ), } } diff --git a/apps/sim/lib/knowledge/connectors/access-token.test.ts b/apps/sim/lib/knowledge/connectors/access-token.test.ts index 4fac4d3b56c..488c5ae82ae 100644 --- a/apps/sim/lib/knowledge/connectors/access-token.test.ts +++ b/apps/sim/lib/knowledge/connectors/access-token.test.ts @@ -343,6 +343,44 @@ describe('delegated connector access', () => { expect(JSON.stringify(syncContextForToken(token!))).toBe('{}') }) + it('forwards cancellation to both the directory token and delegated user token', async () => { + const controller = new AbortController() + const token = await resolveConnectorAccessToken({ + auth: delegationAuth, + accessMode: 'admin', + connector: credentialConnector('service-credential'), + userId: 'actor', + requestId: 'request', + sourceConfig: { adminEmail: 'admin@example.com' }, + signal: controller.signal, + }) + expect(mockResolveTokenBundle).toHaveBeenCalledWith( + 'service-credential', + 'actor', + 'request', + delegationAuth.adminServiceAccountScopes, + 'admin@example.com', + { signal: controller.signal } + ) + await token?.getDelegatedAccessToken?.('employee@example.com') + expect(mockGetServiceAccountToken).toHaveBeenCalledWith( + 'service-credential', + [driveScope], + 'employee@example.com', + { signal: controller.signal } + ) + const child = new AbortController() + await token?.getDelegatedAccessToken?.('second@example.com', child.signal) + const combined = mockGetServiceAccountToken.mock.lastCall?.[3].signal as AbortSignal + expect(combined.aborted).toBe(false) + child.abort() + expect(combined.aborted).toBe(true) + const reason = new DOMException('Caller cancelled', 'AbortError') + controller.abort(reason) + await expect(token?.getDelegatedAccessToken?.('employee@example.com')).rejects.toBe(reason) + expect(mockGetServiceAccountToken).toHaveBeenCalledTimes(2) + }) + it.each([ null, { credentialType: 'oauth', credentialId: 'service-credential', providerId: 'google-drive' }, diff --git a/apps/sim/lib/knowledge/connectors/access-token.ts b/apps/sim/lib/knowledge/connectors/access-token.ts index 82b4996fab3..219303a9321 100644 --- a/apps/sim/lib/knowledge/connectors/access-token.ts +++ b/apps/sim/lib/knowledge/connectors/access-token.ts @@ -30,7 +30,7 @@ export interface ConnectorAccessToken { /** The trusted site domain belonging to the credential's cloud id. */ domain?: string /** Server-only capability bound to this credential and the connector's declared scopes. */ - getDelegatedAccessToken?: (subject: string) => Promise + getDelegatedAccessToken?: (subject: string, signal?: AbortSignal) => Promise } /** @@ -97,8 +97,10 @@ export async function resolveConnectorAccessToken(params: { * person's files. */ sourceConfig: Record + signal?: AbortSignal }): Promise { const { auth, connector, userId, requestId } = params + params.signal?.throwIfAborted() const apiKeyConfig = getConnectorApiKeyConfig(auth) if ( @@ -137,7 +139,14 @@ export async function resolveConnectorAccessToken(params: { requestId, connectorServiceAccountScopes(auth, params.accessMode), subject, - ...(githubRepositoryScope ? [{ githubRepositoryScope }] : []) + ...(githubRepositoryScope || params.signal + ? [ + { + ...(githubRepositoryScope ? { githubRepositoryScope } : {}), + ...(params.signal ? { signal: params.signal } : {}), + }, + ] + : []) ) if (!bundle?.accessToken) return null @@ -155,12 +164,22 @@ export async function resolveConnectorAccessToken(params: { ) { const credentialId = identity.credentialId const scopes = [...auth.serviceAccountDelegationScopes] - getDelegatedAccessToken = async (subject) => { + getDelegatedAccessToken = async (subject, signal) => { + const tokenSignal = + signal && params.signal + ? AbortSignal.any([signal, params.signal]) + : (signal ?? params.signal) + tokenSignal?.throwIfAborted() const email = normalizeEmail(subject) if (!isValidEmailSyntax(email)) { throw new Error('A valid Workspace user email is required for delegated access') } - return getServiceAccountToken(credentialId, scopes, email) + return getServiceAccountToken( + credentialId, + scopes, + email, + ...(tokenSignal ? [{ signal: tokenSignal }] : []) + ) } } } diff --git a/apps/sim/lib/knowledge/connectors/connector-error.test.ts b/apps/sim/lib/knowledge/connectors/connector-error.test.ts index 69a995c95e6..a94305e22b0 100644 --- a/apps/sim/lib/knowledge/connectors/connector-error.test.ts +++ b/apps/sim/lib/knowledge/connectors/connector-error.test.ts @@ -2,7 +2,10 @@ import { DrizzleQueryError } from 'drizzle-orm/errors' import { describe, expect, it } from 'vitest' import { getConnectorFailureDiagnostic } from '@/lib/knowledge/connectors/connector-error' -import { GoogleDriveApiError } from '@/connectors/google-drive/google-drive-errors' +import { + GoogleDriveApiError, + readGoogleDriveApiError, +} from '@/connectors/google-drive/google-drive-errors' import { ConnectorDirectoryError } from '@/connectors/source-error' describe('connector failure diagnostics', () => { @@ -114,6 +117,24 @@ describe('connector failure diagnostics', () => { }) }) + it('preserves safe reason completeness through a wrapped Directory error', async () => { + const cause = await readGoogleDriveApiError( + Response.json({ error: { errors: [{ reason: 'private-unknown-reason' }] } }, { status: 403 }), + 'directory.members.list' + ) + const diagnostic = getConnectorFailureDiagnostic( + new ConnectorDirectoryError('private group', { cause }) + ) + expect(diagnostic).toMatchObject({ + phase: 'directory', + status: 403, + operation: 'directory.members.list', + reasons: [], + reasonState: 'filtered', + }) + expect(JSON.stringify(diagnostic)).not.toContain('private') + }) + it('does not infer status or permanence from a free-form message', () => { expect(getConnectorFailureDiagnostic(new Error('HTTP 403 permission denied'))).toBeNull() expect( diff --git a/apps/sim/lib/knowledge/connectors/connector-error.ts b/apps/sim/lib/knowledge/connectors/connector-error.ts index 737903da280..e82d9715f2c 100644 --- a/apps/sim/lib/knowledge/connectors/connector-error.ts +++ b/apps/sim/lib/knowledge/connectors/connector-error.ts @@ -4,6 +4,7 @@ import { ConnectorDirectoryError, ConnectorSourceError, type ConnectorSourceFailureCategory, + type ConnectorSourceReasonState, } from '@/connectors/source-error' export interface ConnectorFailureDiagnostic { @@ -13,6 +14,7 @@ export interface ConnectorFailureDiagnostic { code?: string operation?: string reasons?: readonly string[] + reasonState?: ConnectorSourceReasonState phase?: 'directory' } diff --git a/apps/sim/lib/knowledge/connectors/google-company-scheduler.test.ts b/apps/sim/lib/knowledge/connectors/google-company-scheduler.test.ts new file mode 100644 index 00000000000..d066c6c422e --- /dev/null +++ b/apps/sim/lib/knowledge/connectors/google-company-scheduler.test.ts @@ -0,0 +1,534 @@ +/** @vitest-environment node */ + +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { createGoogleCompanyScheduler } from '@/lib/knowledge/connectors/google-company-scheduler' +import { + beginListingCheckpoint, + type ListingCheckpoint, + runResumableListing, +} from '@/lib/knowledge/connectors/listing-checkpoint' +import type { + ConnectorPartitionWorkChanges, + ConnectorPartitionWorkItem, + ConnectorPartitionWorkKind, + ConnectorPartitionWorkStore, +} from '@/lib/knowledge/connectors/partition-work' +import { GoogleDriveApiError } from '@/connectors/google-drive/google-drive-errors' +import { GoogleApiError } from '@/connectors/google-workspace/api-errors' +import { googleCompanyUserContextSchema } from '@/connectors/google-workspace/company-work' +import type { GoogleWorkspaceUser } from '@/connectors/google-workspace/users' +import { ConnectorSourceError } from '@/connectors/source-error' +import type { ConnectorConfig, ExternalDocument, ExternalListingFailures } from '@/connectors/types' + +const mocks = vi.hoisted(() => ({ directory: vi.fn() })) +vi.mock('@/connectors/google-workspace/users', async (original) => ({ + ...(await original()), + listGoogleWorkspaceUsers: mocks.directory, +})) + +const document: ExternalDocument = { + externalId: 'doc', + title: 'Document', + content: 'Text', + contentHash: 'hash', + mimeType: 'text/plain', +} +function user(id: string): GoogleWorkspaceUser { + return { id, email: `${id}@fixture.test`, customerId: 'customer', active: true } +} +interface FakeWork extends ConnectorPartitionWorkItem { + complete: boolean + retryAt: Date + served: number + failure?: ExternalListingFailures['samples'][number] +} +const expiredCursor = new Error('expired provider cursor') +function fixture(provider = 'google_calendar', syncIntervalMinutes = 60) { + let clock = new Date('2026-09-17T00:00:00Z') + let served = 0 + const rows = new Map() + const key = (id: string, kind: ConnectorPartitionWorkKind) => `${id}:${kind}` + const incomplete = (row: FakeWork) => + row.kind === 'content' ? !row.complete : Boolean(row.cursor || row.failure) + const store: ConnectorPartitionWorkStore = { + get: async (id, kind) => rows.get(key(id, kind)) ?? null, + next: async (kind, now) => + [...rows.values()] + .filter( + (row) => + row.kind === kind && + row.retryAt <= now && + (kind !== 'content' || + !row.complete || + (syncIntervalMinutes > 0 && [...rows.values()].some(incomplete))) && + (kind === 'content' || (rows.get(key(row.partitionKey, 'content'))?.served ?? 0) > 0) + ) + .sort((a, b) => a.served - b.served || a.partitionKey.localeCompare(b.partitionKey))[0] ?? + null, + remaining: async () => { + const remaining = [...rows.values()].filter(incomplete) + const retryable = [...rows.values()].filter( + (row) => incomplete(row) || (row.kind === 'content' && syncIntervalMinutes > 0) + ) + const failed = [...rows.values()].filter((row) => row.failure) + return { + count: new Set(remaining.map((row) => row.partitionKey)).size, + retryAt: remaining.length + ? (retryable.sort((a, b) => +a.retryAt - +b.retryAt)[0]?.retryAt ?? null) + : null, + ...(failed.length + ? { + failures: { + count: failed.length, + samples: failed.map((row) => row.failure!).slice(0, 10), + }, + } + : {}), + } + }, + } + const apply = (changes: ConnectorPartitionWorkChanges) => { + for (const item of changes.enqueue ?? []) { + if (rows.has(key(item.partitionKey, 'content'))) continue + rows.set(key(item.partitionKey, 'content'), { + ...item, + context: { ...googleCompanyUserContextSchema.parse(item.context), active: true }, + kind: 'content', + attempts: 0, + hasFailure: false, + complete: false, + retryAt: clock, + served: 0, + }) + rows.set(key(item.partitionKey, 'permissions'), { + partitionKey: item.partitionKey, + context: { ...googleCompanyUserContextSchema.parse(item.context), active: true }, + kind: 'permissions', + attempts: 0, + hasFailure: false, + complete: false, + retryAt: new Date(+clock + 12 * 60 * 60 * 1000), + served: 0, + }) + } + if (changes.pin) + Object.assign(rows.get(key(changes.pin.partitionKey, changes.pin.kind))!, { + cursor: changes.pin.cursor, + permissionStartedAt: changes.pin.permissionStartedAt, + }) + if (changes.update) { + const change = changes.update + Object.assign(rows.get(key(change.partitionKey, change.kind))!, { + cursor: change.cursor ?? undefined, + complete: change.kind === 'content' && change.completed, + retryAt: change.retryAt, + attempts: change.attempts, + failure: change.failure ?? undefined, + served: ++served, + permissionStartedAt: change.permissionStartedAt ?? undefined, + }) + for (const kind of ['content', 'permissions'] as const) { + rows.get(key(change.partitionKey, kind))!.hasFailure = Boolean( + rows.get(key(change.partitionKey, 'content'))?.failure || + rows.get(key(change.partitionKey, 'permissions'))?.failure + ) + } + } + } + const list = vi.fn(async () => ({ + documents: [document], + hasMore: false, + })) + let scheduler = createGoogleCompanyScheduler({ + provider, + store, + listDocuments: list, + syncIntervalMinutes, + isListingCursorInvalidError: (error) => error === expiredCursor, + now: () => clock, + }) + let saved = beginListingCheckpoint({ + fingerprint: 'a'.repeat(64), + generationId: 'generation', + startedAt: clock, + }) + const process = vi.fn(async () => undefined) + const step = async (maxPages = 1) => { + saved = await runResumableListing({ + connectorConfig: { + listDocuments: scheduler.listDocuments, + isListingCursorInvalidError: (error) => error === expiredCursor, + }, + sourceConfig: {}, + syncContext: {}, + checkpoint: saved, + deadlineAt: Date.now() + 60_000, + maxPages, + beforePage: async () => undefined, + getAccessToken: async () => 'directory-token', + processPage: process, + saveCheckpoint: async (checkpoint) => { + const changes = scheduler.changesFor(checkpoint.cursor) + if (changes) apply(changes) + saved = structuredClone(checkpoint) + scheduler.didCommit(checkpoint.cursor) + }, + }) + return saved + } + return { + rows, + list, + process, + step, + saved: () => saved, + setSaved: (checkpoint: ListingCheckpoint) => { + saved = checkpoint + }, + advance: (ms: number) => { + clock = new Date(+clock + ms) + }, + restart: () => { + scheduler = createGoogleCompanyScheduler({ + provider, + store, + listDocuments: list, + syncIntervalMinutes, + isListingCursorInvalidError: (error) => error === expiredCursor, + now: () => clock, + }) + }, + } +} + +beforeEach(() => { + mocks.directory.mockReset() +}) + +describe('durable Google company user scheduling', () => { + it('reaches later Directory pages before draining a large first mailbox, then rotates its exact cursor', async () => { + mocks.directory + .mockResolvedValueOnce({ users: [user('a'), user('b')], nextPageToken: 'directory-2' }) + .mockResolvedValueOnce({ users: [user('z')] }) + const f = fixture() + f.list.mockImplementation(async (_token, _config, cursor) => { + if (cursor?.includes('large-page')) return { documents: [], hasMore: false } + return { documents: [document], hasMore: true, nextCursor: 'large-page-2' } + }) + await f.step(6) + expect(mocks.directory).toHaveBeenCalledTimes(2) + expect(f.rows.get('z:content')?.served).toBeGreaterThan(0) + expect(f.list.mock.calls.some((call) => call[2] === 'large-page-2')).toBe(true) + expect(f.saved().cursor!.length).toBeLessThan(2048) + }) + + it.each(['google_calendar', 'google_drive'])( + 'retains an unresolved %s user and continues other users', + async (provider) => { + mocks.directory.mockResolvedValue({ users: [user('a'), user('z')] }) + const f = fixture(provider) + f.list.mockRejectedValueOnce( + provider === 'google_drive' + ? new GoogleDriveApiError(403, [], 'drive.files.list', false) + : new GoogleApiError('calendar.events.list', 403, [], false) + ) + await f.step(4) + expect(f.rows.get('a:content')).toMatchObject({ + complete: false, + attempts: 1, + failure: { status: 403, reasons: [] }, + }) + expect(f.rows.get('z:content')?.complete).toBe(true) + expect(f.saved()).toMatchObject({ + complete: false, + unsafe: true, + listingFailures: { count: 1 }, + }) + expect(f.saved().resumeAt).not.toBeNull() + f.advance(10 * 60 * 1000) + f.restart() + await f.step(3) + expect(f.rows.get('a:content')?.complete).toBe(true) + } + ) + + it.each(['insufficientPermissions', 'rateLimitExceeded', 'SERVICE_DISABLED'])( + 'keeps %s as a connector-level failure', + async (reason) => { + mocks.directory.mockResolvedValue({ users: [user('a')] }) + const f = fixture() + const error = new GoogleApiError('calendar.events.list', 403, [reason]) + f.list.mockRejectedValue(error) + await f.step() + await expect(f.step()).rejects.toBe(error) + expect(f.rows.get('a:content')?.attempts).toBe(0) + } + ) + + it('bounds a run of unresolved user errors rather than marking the tenant complete', async () => { + mocks.directory.mockResolvedValue({ users: ['a', 'b', 'c', 'd'].map(user) }) + const f = fixture() + f.list.mockRejectedValue(new GoogleApiError('calendar.events.list', 403, [], false)) + await f.step(25) + expect(f.list).toHaveBeenCalledTimes(3) + expect(f.saved()).toMatchObject({ complete: false, unsafe: true }) + expect(f.rows.get('d:content')?.served).toBe(0) + expect(f.saved().resumeAt).toBe('2026-09-17T00:05:00.000Z') + }) + + it.each([ + { + provider: 'gmail', + prefix: 'google-workspace:v1:', + state: { provider: 'gmail', providerCursor: 'existing-page-91' }, + }, + { + provider: 'google_calendar', + prefix: 'google-workspace:v1:', + state: { provider: 'google_calendar', providerCursor: 'existing-page-91' }, + }, + { + provider: 'google_drive', + prefix: 'gdrive-company:v1:', + state: { scope: { kind: 'user', cursor: 'existing-page-91' } }, + }, + { + provider: 'google_drive', + prefix: 'gdrive-company:v1:', + state: { scope: { kind: 'drives', pageToken: 'existing-drives-page-91' } }, + }, + { + provider: 'google_drive', + prefix: 'gdrive-company:v1:', + state: { + scope: { + kind: 'drive', + driveIds: ['shared-drive'], + nextPageToken: 'next-drives-page', + cursor: 'existing-page-91', + }, + }, + }, + ])( + 'adopts $provider legacy state $state without resetting the active page', + async ({ provider, prefix, state }) => { + mocks.directory.mockResolvedValue({ users: [user('a'), user('z')] }) + const f = fixture(provider) + const legacy = `${prefix}${Buffer.from(JSON.stringify({ ...state, users: [user('a')], nextUsersPageToken: 'old-directory' })).toString('base64url')}` + const generationId = f.saved().generationId + f.setSaved({ ...f.saved(), cursor: legacy, listedCount: 10 }) + await f.step(3) + expect(f.list.mock.calls[0]?.[2]).toContain(prefix) + const resume = JSON.parse( + Buffer.from(f.list.mock.calls[0]![2]!.slice(prefix.length), 'base64url').toString() + ) + expect(resume).toMatchObject(state) + expect(resume.users).toEqual([{ id: 'a', email: 'a@fixture.test', customerId: 'customer' }]) + expect(f.saved()).toMatchObject({ generationId, listedCount: 11 }) + expect(mocks.directory.mock.calls[0]?.[1]).toBeUndefined() + } + ) + + it('pins the provider snapshot before processing and replays it after interruption', async () => { + mocks.directory.mockResolvedValue({ users: [user('a')] }) + const f = fixture() + f.list.mockResolvedValue({ + documents: [document], + currentCursor: 'stable-provider-snapshot', + hasMore: true, + nextCursor: 'next-provider-page', + }) + await f.step() + f.process.mockRejectedValueOnce(new Error('interrupted')) + await expect(f.step()).rejects.toThrow('interrupted') + expect(f.rows.get('a:content')?.cursor).toBe('stable-provider-snapshot') + expect(f.saved().listedCount).toBe(0) + f.restart() + await f.step() + expect(f.list.mock.calls.at(-1)?.[2]).toBe('stable-provider-snapshot') + expect(f.rows.get('a:content')?.cursor).toBe('next-provider-page') + }) + + it('refreshes permissions with a separate cursor while content remains unfinished', async () => { + mocks.directory.mockResolvedValue({ users: [user('a'), user('z')] }) + const f = fixture() + f.list.mockResolvedValue({ documents: [document], hasMore: true, nextCursor: 'next-page' }) + await f.step(2) + const contentCursor = f.rows.get('a:content')?.cursor + f.advance(13 * 60 * 60 * 1000) + await f.step(2) + expect(f.process.mock.calls.at(-1)?.[2]).toMatchObject({ permissionsOnly: true }) + expect(f.rows.get('a:content')?.cursor).toBe(contentCursor) + expect(f.rows.get('a:permissions')?.cursor).toBe('next-page') + expect(f.saved().listedCount).toBe(1) + await f.step() + expect(f.rows.get('z:content')?.served).toBeGreaterThan(0) + }) + + it('restarts expired Directory discovery without losing committed user work', async () => { + mocks.directory + .mockResolvedValueOnce({ users: [user('a')], nextPageToken: 'expired' }) + .mockRejectedValueOnce(new ConnectorSourceError('expired', 400)) + .mockResolvedValueOnce({ users: [user('a'), user('z')] }) + const f = fixture() + f.list.mockResolvedValue({ + documents: [document], + hasMore: true, + nextCursor: 'saved-user-page', + }) + await f.step(4) + expect(f.rows.get('a:content')?.cursor).toBe('saved-user-page') + expect(f.rows.has('z:content')).toBe(true) + }) + + it('discovers new employees and rescans healthy users while an unavailable mailbox stays blocked', async () => { + mocks.directory + .mockResolvedValueOnce({ users: [user('a'), user('z')] }) + .mockResolvedValue({ users: [user('a'), user('new'), user('z')] }) + const f = fixture() + f.list.mockImplementation(async (_token, _config, cursor) => { + const state = JSON.parse(Buffer.from(cursor!.split(':v1:')[1], 'base64url').toString()) + if (state.users[0].id === 'a') + return { + documents: [], + hasMore: false, + listingFailures: { + count: 1, + samples: [ + { + scope: 'a@fixture.test', + operation: 'directory.users.get', + reasons: ['mailboxNotSetup'], + }, + ], + }, + } + return { documents: [{ ...document, externalId: state.users[0].id }], hasMore: false } + }) + await f.step(4) + const oldCount = f.saved().listedCount + expect(f.rows.get('a:content')?.complete).toBe(false) + expect(f.rows.get('z:content')?.complete).toBe(true) + f.advance(61 * 60_000) + f.restart() + await f.step(5) + expect(f.rows.get('new:content')?.complete).toBe(true) + expect(f.rows.get('z:content')?.complete).toBe(true) + expect(f.saved().listedCount).toBe(oldCount + 2) + expect(f.saved().complete).toBe(false) + }) + + it('rejects an oversized continuation before making provider requests', async () => { + const f = fixture() + f.setSaved({ ...f.saved(), cursor: `google-company-work:v2:${'a'.repeat(20_000)}` }) + await expect(f.step()).rejects.toThrow('too large') + expect(mocks.directory).not.toHaveBeenCalled() + expect(f.list).not.toHaveBeenCalled() + }) + + it.each(['content', 'permissions'] as const)( + 'resets only the expired %s cursor, including interruption before the recovery commit', + async (kind) => { + mocks.directory.mockResolvedValue({ users: [user('a'), user('b')] }) + const f = fixture('google_drive') + await f.step() + const a = f.rows.get(`a:${kind}`)! + a.cursor = 'expired-inner-cursor' + a.retryAt = new Date('2026-09-17T00:00:00Z') + a.permissionStartedAt = new Date('2026-09-16T00:00:00Z') + if (kind === 'permissions') { + const content = f.rows.get('a:content')! + content.served = 1 + content.retryAt = new Date('2026-09-18T00:00:00Z') + } + const b = f.rows.get('b:content')! + b.cursor = 'b-content-page-42' + b.retryAt = new Date('2026-09-18T00:00:00Z') + f.rows.get('b:permissions')!.cursor = 'b-permission-page-7' + const beforeB = structuredClone(b) + f.list.mockRejectedValue(expiredCursor) + f.process.mockRejectedValueOnce(new Error('interrupted before recovery commit')) + await expect(f.step()).rejects.toThrow('interrupted before recovery commit') + expect(a.cursor).toBe('expired-inner-cursor') + expect(f.saved().generationId).toBe('generation') + f.restart() + await f.step(3) + expect(a.cursor).toContain('gdrive-company:v1:') + expect(a.complete).toBe(false) + expect(a.attempts).toBe(1) + expect(a.retryAt).toEqual(new Date('2026-09-17T01:00:00Z')) + if (kind === 'permissions') expect(a.permissionStartedAt).toBeUndefined() + expect(f.rows.get('b:content')).toEqual(beforeB) + expect(f.rows.get('b:permissions')?.cursor).toBe('b-permission-page-7') + expect(f.saved()).toMatchObject({ + generationId: 'generation', + listedCount: 0, + complete: false, + }) + expect(f.list).toHaveBeenCalledTimes(2) + } + ) + + it('schedules completed users at the supplied refresh cadence', async () => { + mocks.directory.mockResolvedValue({ users: [user('a'), user('b')] }) + const f = fixture('gmail', 15) + await f.step(2) + expect(f.rows.get('a:content')?.retryAt).toEqual(new Date('2026-09-17T00:15:00Z')) + }) + + it.each([0, 60])( + 'waits for deferred permissions at cadence %i, then reaches EOF without waiting for periodic refresh', + async (cadence) => { + mocks.directory.mockResolvedValue({ users: [user('a')] }) + const f = fixture('google_calendar', cadence) + await f.step(2) + const content = structuredClone(f.rows.get('a:content')) + const permission = f.rows.get('a:permissions')! + permission.cursor = 'permission-page-2' + permission.retryAt = new Date('2026-09-17T00:00:00Z') + f.list.mockRejectedValueOnce(new GoogleApiError('calendar.events.list', 403, [], false)) + await f.step(2) + expect(f.saved()).toMatchObject({ + generationId: 'generation', + complete: false, + resumeAt: '2026-09-17T00:05:00.000Z', + listingFailures: { count: 1 }, + }) + expect(permission.cursor).toBe('permission-page-2') + const callsBeforeRetry = f.list.mock.calls.length + f.restart() + await f.step() + expect(f.list).toHaveBeenCalledTimes(callsBeforeRetry) + expect(f.saved().complete).toBe(false) + f.advance(5 * 60_000) + f.restart() + await f.step(2) + expect(f.list.mock.calls.at(-1)?.[2]).toBe('permission-page-2') + expect(f.rows.get('a:content')).toEqual(content) + expect(f.saved()).toMatchObject({ + generationId: 'generation', + complete: true, + resumeAt: null, + listingFailures: null, + }) + expect(permission.retryAt).toEqual(new Date('2026-09-17T12:05:00Z')) + expect(mocks.directory).toHaveBeenCalledTimes(1) + } + ) + + it('keeps completed manual users complete while unfinished work and permission refresh continue', async () => { + mocks.directory.mockResolvedValue({ users: [user('a'), user('b')] }) + const f = fixture('gmail', 0) + await f.step(2) + const a = f.rows.get('a:content')! + const before = structuredClone(a) + const b = f.rows.get('b:content')! + b.retryAt = new Date('2026-09-18T00:00:00Z') + f.advance(13 * 60 * 60 * 1000) + await f.step(2) + expect(f.rows.get('a:content')).toEqual(before) + expect(f.process.mock.calls.at(-2)?.[2]).toMatchObject({ permissionsOnly: true }) + expect(mocks.directory).toHaveBeenCalledTimes(1) + expect(f.saved().resumeAt).toBe('2026-09-18T00:00:00.000Z') + }) +}) diff --git a/apps/sim/lib/knowledge/connectors/google-company-scheduler.ts b/apps/sim/lib/knowledge/connectors/google-company-scheduler.ts new file mode 100644 index 00000000000..8acdd0b9cfe --- /dev/null +++ b/apps/sim/lib/knowledge/connectors/google-company-scheduler.ts @@ -0,0 +1,382 @@ +import { z } from 'zod' +import type { + ConnectorPartitionWorkChanges, + ConnectorPartitionWorkStore, +} from '@/lib/knowledge/connectors/partition-work' +import { googleDriveCompanyCursorAdapter } from '@/connectors/google-drive/company-crawl' +import { GoogleDriveApiError } from '@/connectors/google-drive/google-drive-errors' +import { GoogleApiError } from '@/connectors/google-workspace/api-errors' +import { googleWorkspaceCompanyCursorAdapter } from '@/connectors/google-workspace/company-crawl' +import type { + GoogleCompanyCursorAdapter, + GoogleCompanyUserWork, +} from '@/connectors/google-workspace/company-work' +import { + listGoogleWorkspaceUsers, + selectedGoogleWorkspaceUsers, +} from '@/connectors/google-workspace/users' +import { MAX_LISTING_FAILURE_SAMPLES } from '@/connectors/listing-failures' +import { ConnectorSourceError } from '@/connectors/source-error' +import type { + ConnectorConfig, + ExternalDocumentList, + ExternalListingFailures, +} from '@/connectors/types' + +const CURSOR_PREFIX = 'google-company-work:v2:' +export const GOOGLE_COMPANY_PERMISSION_REFRESH_MS = 12 * 60 * 60 * 1000 +const DIRECTORY_REFRESH_MS = 60 * 60 * 1000 +const MAX_UNRESOLVED_FAILURES_PER_PASS = 3 +const cursorSchema = z.object({ + directoryComplete: z.boolean(), + directoryCursor: z.string().max(8192).optional(), + directoryRefreshAt: z.string().datetime().optional(), + phase: z.enum(['directory', 'users']), + revision: z.number().int().nonnegative(), + permissionTurn: z.boolean(), + active: z + .object({ userId: z.string().min(1).max(256), kind: z.enum(['content', 'permissions']) }) + .optional(), +}) +type CompanyWorkCursor = z.infer +type ListingFailure = ExternalListingFailures['samples'][number] + +function adapterFor(provider: string): GoogleCompanyCursorAdapter | null { + if (provider === 'google_drive') return googleDriveCompanyCursorAdapter + if (provider === 'gmail' || provider === 'google_calendar') + return googleWorkspaceCompanyCursorAdapter(provider) + return null +} + +export function isGoogleCompanySource(provider: string, context: Record): boolean { + return ( + adapterFor(provider) !== null && + context.mirrorsSourceAcls === true && + typeof context.getDelegatedAccessToken === 'function' + ) +} + +function writeCursor(state: CompanyWorkCursor): string { + return `${CURSOR_PREFIX}${Buffer.from(JSON.stringify(state)).toString('base64url')}` +} + +function readCursor(cursor: string): CompanyWorkCursor { + if (cursor.length > 16 * 1024) throw new Error('Google company work cursor is too large') + return cursorSchema.parse( + JSON.parse(Buffer.from(cursor.slice(CURSOR_PREFIX.length), 'base64url').toString('utf8')) + ) +} + +/** An unresolved per-user 403 remains retryable partial coverage, never proof that access was revoked. */ +function deferredUserFailure( + error: unknown, + user: GoogleCompanyUserWork['user'] +): ListingFailure | null { + if ( + (!(error instanceof GoogleApiError) && !(error instanceof GoogleDriveApiError)) || + error.status !== 403 || + !error.diagnostic + ) + return null + const { operation, reasons } = error.diagnostic + const userListing = + operation === 'calendar.events.list' || + operation === 'gmail.threads.list' || + operation === 'drive.files.list' || + operation === 'drive.drives.list' + const globalReasons = new Set([ + 'accessNotConfigured', + 'authError', + 'insufficientPermissions', + 'domainPolicy', + 'serviceDisabled', + 'ACCESS_TOKEN_SCOPE_INSUFFICIENT', + 'API_KEY_SERVICE_BLOCKED', + 'SERVICE_DISABLED', + 'USER_PROJECT_DENIED', + 'dailyLimitExceeded', + 'quotaExceeded', + ]) + if (!userListing || error.rateLimited || reasons.some((reason) => globalReasons.has(reason))) + return null + return { + scope: user.email, + operation, + status: error.status, + reasons: [...reasons], + ...(error.diagnostic.reasonState ? { reasonState: error.diagnostic.reasonState } : {}), + } +} + +/** Durable fair scheduling adds no parallel provider or database work. */ +export function createGoogleCompanyScheduler(input: { + provider: string + store: ConnectorPartitionWorkStore + listDocuments: ConnectorConfig['listDocuments'] + isListingCursorInvalidError?: ConnectorConfig['isListingCursorInvalidError'] + syncIntervalMinutes: number + now?: () => Date +}) { + const adapter = adapterFor(input.provider) + if (!adapter) throw new Error('Unsupported Google company provider') + const now = input.now ?? (() => new Date()) + const changes = new Map() + let unresolvedFailures = 0 + let permissionStartedAt: Date | undefined + const initial = (): CompanyWorkCursor => ({ + directoryComplete: false, + phase: 'directory', + revision: 0, + permissionTurn: false, + }) + const nextCursor = (state: CompanyWorkCursor, change: ConnectorPartitionWorkChanges): string => { + const cursor = writeCursor({ ...state, active: undefined, revision: state.revision + 1 }) + changes.set(cursor, change) + return cursor + } + const listDocuments: ConnectorConfig['listDocuments'] = async ( + accessToken, + sourceConfig, + cursor, + syncContext + ) => { + permissionStartedAt = undefined + const signal = syncContext?.signal instanceof AbortSignal ? syncContext.signal : undefined + signal?.throwIfAborted() + if (cursor && !cursor.startsWith(CURSOR_PREFIX)) { + return { + documents: [], + hasMore: true, + nextCursor: nextCursor(initial(), { + enqueue: adapter + .resume(cursor) + .map(({ user, cursor }) => ({ partitionKey: user.id, context: user, cursor })), + }), + } + } + let state = cursor ? readCursor(cursor) : initial() + if ( + !state.active && + input.syncIntervalMinutes > 0 && + state.directoryComplete && + state.directoryRefreshAt && + new Date(state.directoryRefreshAt) <= now() + ) { + state = { ...state, directoryComplete: false, directoryCursor: undefined, phase: 'directory' } + } + const selected = selectedGoogleWorkspaceUsers(sourceConfig.userEmails) + if (!state.active && !state.directoryComplete && state.phase === 'directory') { + let page: Awaited> + try { + page = await listGoogleWorkspaceUsers(accessToken, state.directoryCursor, signal) + } catch (error) { + if ( + !state.directoryCursor || + !(error instanceof ConnectorSourceError) || + error.status !== 400 + ) + throw error + /** Re-enumeration is idempotent and preserves every already committed user continuation. */ + return { + documents: [], + hasMore: true, + nextCursor: nextCursor({ ...state, directoryCursor: undefined }, {}), + } + } + const users = page.users.filter( + (user) => user.active && (!selected.length || selected.includes(user.email)) + ) + return { + documents: [], + currentCursor: writeCursor(state), + hasMore: true, + nextCursor: nextCursor( + { + ...state, + phase: 'users', + directoryCursor: page.nextPageToken, + directoryComplete: !page.nextPageToken, + directoryRefreshAt: page.nextPageToken + ? undefined + : new Date(now().getTime() + DIRECTORY_REFRESH_MS).toISOString(), + }, + { + enqueue: users.map(({ id, email, customerId }) => { + const user = { id, email, customerId } + return { partitionKey: user.id, context: user, cursor: adapter.seed(user) } + }), + } + ), + } + } + const preferred = state.permissionTurn ? 'permissions' : 'content' + const work = state.active + ? await input.store.get(state.active.userId, state.active.kind) + : ((await input.store.next(preferred, now())) ?? + (await input.store.next(preferred === 'content' ? 'permissions' : 'content', now()))) + if (!work) { + if (state.active) throw new Error('Google company crawl lost its durable user checkpoint') + if (!state.directoryComplete) + return { + documents: [], + hasMore: true, + nextCursor: nextCursor({ ...state, phase: 'directory' }, {}), + } + const remaining = await input.store.remaining() + return { + documents: [], + hasMore: remaining.count > 0, + ...(remaining.count > 0 + ? { + nextCursor: nextCursor(state, {}), + resumeAt: new Date( + Math.min( + remaining.retryAt?.getTime() ?? now().getTime() + 60_000, + input.syncIntervalMinutes > 0 && state.directoryRefreshAt + ? new Date(state.directoryRefreshAt).getTime() + : Number.POSITIVE_INFINITY + ) + ).toISOString(), + } + : {}), + listingFailures: remaining.failures ?? null, + ...(remaining.failures ? { reconciliationSafe: false } : {}), + } + } + const active = { + ...state, + revision: state.revision + 1, + active: { userId: work.partitionKey, kind: work.kind }, + } + const currentCursor = writeCursor(active) + const next = { + ...state, + revision: active.revision, + phase: state.directoryComplete ? 'users' : 'directory', + permissionTurn: work.kind === 'content', + } as CompanyWorkCursor + const failed = async ( + failure: ListingFailure, + unresolved = false, + resetCursor = false + ): Promise => { + const previous = await input.store.remaining() + const retryAt = new Date( + now().getTime() + + (!unresolved ? 60 : Math.min(60, 5 * 2 ** Math.min(work.attempts, 4))) * 60_000 + ) + if (unresolved) unresolvedFailures += 1 + const failureSamples = [ + failure, + ...(previous.failures?.samples ?? []).filter((item) => item.scope !== failure.scope), + ] + return { + documents: [], + currentCursor, + hasMore: true, + nextCursor: nextCursor(next, { + update: { + partitionKey: work.partitionKey, + kind: work.kind, + cursor: resetCursor ? adapter.seed(work.context) : (work.cursor ?? null), + completed: false, + retryAt, + attempts: work.attempts + 1, + failure, + ...(resetCursor && work.kind === 'permissions' ? { permissionStartedAt: null } : {}), + }, + }), + reconciliationSafe: false, + listingFailures: { + count: (previous.failures?.count ?? 0) + (work.hasFailure ? 0 : 1), + samples: failureSamples.slice(0, MAX_LISTING_FAILURE_SAMPLES), + }, + ...(unresolvedFailures >= MAX_UNRESOLVED_FAILURES_PER_PASS + ? { resumeAt: new Date(now().getTime() + 5 * 60_000).toISOString() } + : {}), + } + } + let page: ExternalDocumentList + try { + page = await input.listDocuments( + accessToken, + sourceConfig, + work.cursor ?? adapter.seed(work.context), + syncContext + ) + } catch (error) { + signal?.throwIfAborted() + if (input.isListingCursorInvalidError?.(error)) + return failed( + { scope: work.context.email, operation: 'google.user.cursor', reasons: [] }, + false, + true + ) + const failure = deferredUserFailure(error, work.context) + if (!failure) throw error + return failed(failure, true) + } + if (page.listingFailures) + return failed( + page.listingFailures.samples[0] ?? { + scope: work.context.email, + operation: 'google.user.list', + reasons: [], + } + ) + if ( + page.hasMore && + (!page.nextCursor || page.nextCursor === (page.currentCursor ?? work.cursor)) + ) { + throw new Error('Google user listing did not advance its provider continuation') + } + unresolvedFailures = 0 + permissionStartedAt = + work.kind === 'permissions' ? (work.permissionStartedAt ?? now()) : undefined + if (page.currentCursor) + changes.set(currentCursor, { + pin: { + partitionKey: work.partitionKey, + kind: work.kind, + cursor: page.currentCursor, + permissionStartedAt, + }, + }) + return { + ...page, + currentCursor, + hasMore: true, + ...(work.kind === 'permissions' ? { permissionsOnly: true } : {}), + nextCursor: nextCursor(next, { + update: { + partitionKey: work.partitionKey, + kind: work.kind, + cursor: page.nextCursor ?? null, + completed: !page.hasMore, + attempts: 0, + failure: null, + retryAt: !page.hasMore + ? new Date( + now().getTime() + + (work.kind === 'permissions' + ? GOOGLE_COMPANY_PERMISSION_REFRESH_MS + : input.syncIntervalMinutes * 60_000) + ) + : now(), + ...(work.kind === 'permissions' + ? { permissionStartedAt: page.hasMore ? permissionStartedAt : null } + : {}), + }, + }), + } + } + return { + listDocuments, + permissionStartedAt: () => permissionStartedAt, + changesFor: (cursor: string | null): ConnectorPartitionWorkChanges | undefined => + cursor ? changes.get(cursor) : undefined, + didCommit: (cursor: string | null) => { + if (cursor) changes.delete(cursor) + }, + } +} diff --git a/apps/sim/lib/knowledge/connectors/listing-checkpoint.test.ts b/apps/sim/lib/knowledge/connectors/listing-checkpoint.test.ts index fede008e1a3..5ef9f2ec0dc 100644 --- a/apps/sim/lib/knowledge/connectors/listing-checkpoint.test.ts +++ b/apps/sim/lib/knowledge/connectors/listing-checkpoint.test.ts @@ -9,6 +9,14 @@ import { readListingCheckpoint, runResumableListing, } from '@/lib/knowledge/connectors/listing-checkpoint' +import { + googleDriveCompanyCursorAdapter, + InvalidGoogleCompanyCursor, +} from '@/connectors/google-drive/company-crawl' +import { + googleWorkspaceCompanyCursorAdapter, + InvalidGoogleWorkspaceCursor, +} from '@/connectors/google-workspace/company-crawl' import type { ExternalDocument } from '@/connectors/types' const fingerprint = listingFingerprint({ source: 'drive', folder: 'folder-1' }) @@ -234,6 +242,92 @@ describe('durable connector listing checkpoints', () => { expect(f.processPage.mock.calls[0][1].generationId).toBe(result.generationId) }) + it.each(['google_drive', 'gmail', 'google_calendar'] as const)( + 'safely restarts when the legacy %s reader receives a partition checkpoint', + async (provider) => { + const adapter = + provider === 'google_drive' + ? googleDriveCompanyCursorAdapter + : googleWorkspaceCompanyCursorAdapter(provider) + const InvalidCursorError = + provider === 'google_drive' ? InvalidGoogleCompanyCursor : InvalidGoogleWorkspaceCursor + const cursor = `google-company-work:v2:${Buffer.from( + JSON.stringify({ + directoryComplete: true, + directoryRefreshAt: '2026-09-08T11:00:00.000Z', + phase: 'users', + revision: 42, + permissionTurn: true, + active: { userId: 'user-1', kind: 'permissions' }, + }) + ).toString('base64url')}` + const f = fixture({ + ...checkpoint(), + cursor, + listedCount: 700, + unsafe: true, + contentFailures: true, + permissionFailures: true, + listingFailures: { + count: 1, + samples: [{ scope: 'user@example.com', operation: 'google.user.list', reasons: [] }], + }, + }) + const databaseTime = new Date('2026-09-08T10:00:00.000Z') + const getGenerationStartedAt = vi.fn(async () => databaseTime) + f.listDocuments.mockImplementation(async (_token, _source, savedCursor) => { + expect(savedCursor).toBe(cursor) + expect(() => adapter.resume(savedCursor)).toThrow(InvalidCursorError) + adapter.resume(savedCursor) + return { documents: [doc], hasMore: false } + }) + /** The provider readers and restart contract are shared with staging; its envelope parser is not emulated. */ + const restarted = await runResumableListing({ + ...f.input, + maxPages: 1, + getGenerationStartedAt, + connectorConfig: { + listDocuments: f.listDocuments, + isListingCursorInvalidError: (error) => error instanceof InvalidCursorError, + }, + }) + expect(f.processPage).not.toHaveBeenCalled() + expect(f.saveCheckpoint).toHaveBeenCalledOnce() + expect(getGenerationStartedAt).toHaveBeenCalledOnce() + expect(restarted.generationId).not.toBe('cycle-1') + expect(restarted).toMatchObject({ + fingerprint, + startedAt: databaseTime.toISOString(), + cursor: null, + complete: false, + listedCount: 0, + unsafe: false, + contentFailures: false, + permissionFailures: false, + listingFailures: null, + }) + expect(f.saved()).toEqual(restarted) + + const resumed = fixture(f.saved()) + resumed.listDocuments.mockResolvedValue({ + documents: [doc], + hasMore: true, + nextCursor: 'restarted-provider-page-2', + }) + const incomplete = await runResumableListing({ ...resumed.input, maxPages: 1 }) + expect(resumed.listDocuments.mock.calls[0][2]).toBeUndefined() + expect(resumed.processPage).toHaveBeenCalledOnce() + expect(resumed.processPage.mock.calls[0][1].generationId).toBe(restarted.generationId) + expect(incomplete).toMatchObject({ + generationId: restarted.generationId, + startedAt: databaseTime.toISOString(), + cursor: 'restarted-provider-page-2', + complete: false, + listedCount: 1, + }) + } + ) + it('finishes a source larger than 50,000 documents without retaining its whole listing', async () => { const f = fixture() let processed = 0 diff --git a/apps/sim/lib/knowledge/connectors/listing-checkpoint.ts b/apps/sim/lib/knowledge/connectors/listing-checkpoint.ts index 476a8bfb86a..47cc5ed4b00 100644 --- a/apps/sim/lib/knowledge/connectors/listing-checkpoint.ts +++ b/apps/sim/lib/knowledge/connectors/listing-checkpoint.ts @@ -31,6 +31,7 @@ const checkpointSchema = z.object({ incrementalSince: z.string().datetime().nullable(), forceRehydrate: z.boolean(), fullSync: z.boolean().default(false), + resumeAt: z.string().datetime().nullable().default(null), }) export type ListingCheckpoint = z.infer @@ -75,6 +76,7 @@ export function beginListingCheckpoint(input: { incrementalSince: input.incrementalSince?.toISOString() ?? null, forceRehydrate: input.forceRehydrate ?? false, fullSync: input.fullSync ?? false, + resumeAt: null, } } @@ -99,7 +101,8 @@ export async function runResumableListing(input: { /** False retains this page's cursor after a bounded amount of durable work. */ processPage: ( documents: ExternalDocument[], - checkpoint: ListingCheckpoint + checkpoint: ListingCheckpoint, + response: ExternalDocumentList ) => Promise saveCheckpoint: (checkpoint: ListingCheckpoint) => Promise }): Promise { @@ -156,14 +159,17 @@ export async function runResumableListing(input: { } checkpoint.unsafe ||= response.reconciliationSafe === false || - response.listingFailures !== undefined || + response.listingFailures != null || Boolean( input.syncContext.listingCapped || input.syncContext.listingTruncated || input.syncContext.reconciliationUnsafe ) - if (response.listingFailures) { - checkpoint.listingFailures = listingFailuresSchema.parse(response.listingFailures) + if (response.listingFailures !== undefined) { + checkpoint.listingFailures = + response.listingFailures === null + ? null + : listingFailuresSchema.parse(response.listingFailures) } if (response.currentCursor !== undefined && response.currentCursor !== checkpoint.cursor) { if (response.currentCursor.length > 512 * 1024) @@ -172,7 +178,7 @@ export async function runResumableListing(input: { await input.saveCheckpoint(checkpoint) cursors.add(response.currentCursor) } - if ((await input.processPage(response.documents, checkpoint)) === false) { + if ((await input.processPage(response.documents, checkpoint, response)) === false) { await input.saveCheckpoint(checkpoint) break } @@ -180,11 +186,14 @@ export async function runResumableListing(input: { ...checkpoint, cursor: response.nextCursor ?? null, complete: !response.hasMore, - listedCount: checkpoint.listedCount + response.documents.length, + listedCount: + checkpoint.listedCount + (response.permissionsOnly ? 0 : response.documents.length), + resumeAt: response.resumeAt ?? null, } await input.saveCheckpoint(next) checkpoint = next if (checkpoint.cursor) cursors.add(checkpoint.cursor) + if (checkpoint.resumeAt) break } return checkpoint } diff --git a/apps/sim/lib/knowledge/connectors/member-sync-engine.ts b/apps/sim/lib/knowledge/connectors/member-sync-engine.ts index addef3346e4..f0e34dd6e53 100644 --- a/apps/sim/lib/knowledge/connectors/member-sync-engine.ts +++ b/apps/sim/lib/knowledge/connectors/member-sync-engine.ts @@ -1446,7 +1446,7 @@ async function completeMemberSync( .select({ id: knowledgeBase.id }) .from(knowledgeBase) .where(and(eq(knowledgeBase.id, run.knowledgeBaseId), isNull(knowledgeBase.deletedAt))) - .for('update') + .for('share') if (!activeKnowledgeBase) { /** Nothing to record against a deleted knowledge base; hand the lease back rather than let it expire as a failure. */ await tx diff --git a/apps/sim/lib/knowledge/connectors/partition-store.ts b/apps/sim/lib/knowledge/connectors/partition-store.ts new file mode 100644 index 00000000000..02009652d1c --- /dev/null +++ b/apps/sim/lib/knowledge/connectors/partition-store.ts @@ -0,0 +1,197 @@ +import { db } from '@sim/db' +import { knowledgeConnectorPartition } from '@sim/db/schema' +import { and, asc, eq, exists, isNotNull, lte, ne, or, sql } from 'drizzle-orm' +import type { DbOrTx } from '@/lib/db/types' +import type { + ConnectorPartitionWorkChanges, + ConnectorPartitionWorkItem, + ConnectorPartitionWorkKind, + ConnectorPartitionWorkStore, +} from '@/lib/knowledge/connectors/partition-work' +import { listingFailuresSchema, MAX_LISTING_FAILURE_SAMPLES } from '@/connectors/listing-failures' + +type Row = typeof knowledgeConnectorPartition.$inferSelect +const work = knowledgeConnectorPartition + +function project( + row: Row, + kind: ConnectorPartitionWorkKind, + parseContext: (value: unknown) => Context +): ConnectorPartitionWorkItem { + return { + partitionKey: row.partitionKey, + context: parseContext(row.context), + kind, + cursor: (kind === 'content' ? row.cursor : row.permissionCursor) ?? undefined, + attempts: kind === 'content' ? row.attempts : row.permissionAttempts, + hasFailure: row.failure !== null || row.permissionFailure !== null, + permissionStartedAt: row.permissionStartedAt ?? undefined, + } +} + +/** The connector's existing lease is the sole writer lease; no extra worker concurrency is introduced. */ +export function connectorPartitionWorkStore( + connectorId: string, + generationId: string, + parseContext: (value: unknown) => Context, + rescanCompleted = true +): ConnectorPartitionWorkStore { + const scope = and(eq(work.connectorId, connectorId), eq(work.generationId, generationId)) + const contentIncomplete = ne(work.status, 'complete') + const permissionsIncomplete = or( + isNotNull(work.permissionCursor), + isNotNull(work.permissionFailure) + ) + const incomplete = or(contentIncomplete, permissionsIncomplete) + return { + async get(partitionKey, kind) { + const [row] = await db + .select() + .from(work) + .where(and(scope, eq(work.partitionKey, partitionKey))) + .limit(1) + return row ? project(row, kind, parseContext) : null + }, + async next(kind, now) { + /** A completed sweep must reach EOF instead of starting another overdue partition sweep. */ + const contentEligible = rescanCompleted + ? or( + contentIncomplete, + exists(db.select({ id: work.partitionKey }).from(work).where(and(scope, incomplete))) + ) + : contentIncomplete + const [row] = await db + .select() + .from(work) + .where( + and( + scope, + kind === 'content' + ? and(lte(work.retryAt, now), contentEligible) + : and(isNotNull(work.lastServedAt), lte(work.permissionRetryAt, now)) + ) + ) + .orderBy( + sql`${kind === 'content' ? work.lastServedAt : work.permissionLastServedAt} ASC NULLS FIRST`, + asc(work.partitionKey) + ) + .limit(1) + return row ? project(row, kind, parseContext) : null + }, + async remaining() { + const [counts] = await db + .select({ + count: sql`count(*) FILTER (WHERE ${incomplete})::int`, + failures: sql`count(*) FILTER (WHERE ${work.failure} IS NOT NULL OR ${work.permissionFailure} IS NOT NULL)::int`, + retryAt: sql`LEAST( + min(${work.retryAt}) FILTER (WHERE ${rescanCompleted ? sql`true` : contentIncomplete}), + min(${work.permissionRetryAt}) FILTER (WHERE ${permissionsIncomplete}) + )`.mapWith(work.retryAt), + }) + .from(work) + .where(scope) + const samples = counts?.failures + ? await db + .select({ failure: work.failure, permissionFailure: work.permissionFailure }) + .from(work) + .where(and(scope, or(isNotNull(work.failure), isNotNull(work.permissionFailure)))) + .orderBy(asc(work.partitionKey)) + .limit(MAX_LISTING_FAILURE_SAMPLES) + : [] + return { + count: counts?.count ?? 0, + retryAt: counts?.count ? counts.retryAt : null, + ...(counts?.failures + ? { + failures: listingFailuresSchema.parse({ + count: counts.failures, + samples: samples.map((row) => row.failure ?? row.permissionFailure), + }), + } + : {}), + } + }, + } +} + +/** Called in the same transaction as the listing checkpoint, after that page's durable work. */ +export async function commitConnectorPartitionWork( + tx: DbOrTx, + connectorId: string, + generationId: string, + changes: ConnectorPartitionWorkChanges, + permissionRefreshAt: Date +): Promise { + const scope = and(eq(work.connectorId, connectorId), eq(work.generationId, generationId)) + if (changes.enqueue?.length) { + const at = new Date() + await tx + .insert(work) + .values( + changes.enqueue.map(({ partitionKey, context, cursor }) => ({ + connectorId, + generationId, + partitionKey, + context, + cursor: cursor ?? null, + permissionRetryAt: permissionRefreshAt, + })) + ) + /** Content restarts must not postpone the earlier deadline for refreshing stored permissions. */ + .onConflictDoUpdate({ + target: [work.connectorId, work.partitionKey], + set: { + generationId, + context: sql`excluded.context`, + cursor: sql`excluded.cursor`, + status: 'pending', + attempts: 0, + retryAt: at, + lastServedAt: null, + failure: null, + permissionCursor: null, + permissionStartedAt: null, + permissionFailure: null, + permissionAttempts: 0, + }, + setWhere: ne(work.generationId, generationId), + }) + } + if (changes.pin) { + const pin = changes.pin + await tx + .update(work) + .set( + pin.kind === 'content' + ? { cursor: pin.cursor } + : { permissionCursor: pin.cursor, permissionStartedAt: pin.permissionStartedAt } + ) + .where(and(scope, eq(work.partitionKey, pin.partitionKey))) + } + if (changes.update) { + const update = changes.update + const at = new Date() + await tx + .update(work) + .set( + update.kind === 'content' + ? { + cursor: update.cursor, + status: update.failure ? 'blocked' : update.completed ? 'complete' : 'pending', + retryAt: update.retryAt, + attempts: update.attempts, + failure: update.failure, + lastServedAt: at, + } + : { + permissionCursor: update.cursor, + permissionRetryAt: update.retryAt, + permissionAttempts: update.attempts, + permissionFailure: update.failure, + permissionLastServedAt: at, + permissionStartedAt: update.permissionStartedAt, + } + ) + .where(and(scope, eq(work.partitionKey, update.partitionKey))) + } +} diff --git a/apps/sim/lib/knowledge/connectors/partition-work.ts b/apps/sim/lib/knowledge/connectors/partition-work.ts new file mode 100644 index 00000000000..a0bb80fb711 --- /dev/null +++ b/apps/sim/lib/knowledge/connectors/partition-work.ts @@ -0,0 +1,54 @@ +import type { ExternalListingFailures } from '@/connectors/types' + +export type ConnectorPartitionWorkKind = 'content' | 'permissions' +type ListingFailure = ExternalListingFailures['samples'][number] + +/** Provider adapters validate their own context; persistence owns only partition progress. */ +export interface ConnectorPartitionWorkItem { + partitionKey: string + context: Context + kind: ConnectorPartitionWorkKind + cursor?: string + attempts: number + hasFailure: boolean + permissionStartedAt?: Date +} + +export interface ConnectorPartitionWorkUpdate { + partitionKey: string + kind: ConnectorPartitionWorkKind + cursor: string | null + completed: boolean + retryAt: Date + attempts: number + failure: ListingFailure | null + permissionStartedAt?: Date | null +} + +/** All writes are supplied to the caller's lease-guarded checkpoint transaction. */ +export interface ConnectorPartitionWorkChanges { + enqueue?: { partitionKey: string; context: Record; cursor?: string }[] + pin?: { + partitionKey: string + kind: ConnectorPartitionWorkKind + cursor: string + permissionStartedAt?: Date + } + update?: ConnectorPartitionWorkUpdate +} + +export interface ConnectorPartitionWorkStore { + get: ( + partitionKey: string, + kind: ConnectorPartitionWorkKind + ) => Promise | null> + next: ( + kind: ConnectorPartitionWorkKind, + now: Date + ) => Promise | null> + remaining: () => Promise<{ + count: number + retryAt: Date | null + failures?: ExternalListingFailures + }> +} diff --git a/apps/sim/lib/knowledge/connectors/sync-content-pass.test.ts b/apps/sim/lib/knowledge/connectors/sync-content-pass.test.ts index 59b87aca767..5ab9a1ee5d1 100644 --- a/apps/sim/lib/knowledge/connectors/sync-content-pass.test.ts +++ b/apps/sim/lib/knowledge/connectors/sync-content-pass.test.ts @@ -288,6 +288,8 @@ async function runPass( checkpoint?: ListingCheckpoint databaseTime?: Date getDocument?: () => Promise + permissionsOnly?: boolean + permissionStoredAfter?: StoredPage } = {} ) { vi.clearAllMocks() @@ -320,6 +322,11 @@ async function runPass( queueTableRows(schemaMock.document, [{ fileUrl: options.existing?.fileUrl ?? '' }]) } } + if (options.permissionsOnly) + queueTableRows( + schemaMock.document, + options.permissionStoredAfter ? [options.permissionStoredAfter] : [] + ) queueTableRows(schemaMock.document, [ { ownedCount: 1, listedCount: 1, softCount: 0, hardCount: 0 }, ]) @@ -346,7 +353,14 @@ async function runPass( options.getDocument ?? (() => confluenceConnector.getDocument('token', SOURCE_CONFIG, 'page', syncContext)) ) - const listDocuments = vi.fn(confluenceConnector.listDocuments) + const listDocuments = vi.fn( + async (...args: Parameters) => { + const page = await confluenceConnector.listDocuments(...args) + return options.permissionsOnly + ? { ...page, permissionsOnly: true, reconciliationSafe: false } + : page + } + ) const pass = await runConnectorContentPass({ connectorId: 'connector', connector: { @@ -385,6 +399,28 @@ function contentWrite(): Record { } describe('content pass checkpoint intent', () => { + it('preserves non-Google replay behavior for already-seen documents', async () => { + sourceBody = { value: '

    Updated body

    ' } + const checkpoint = beginListingCheckpoint({ + fingerprint: 'a'.repeat(64), + generationId: 'incomplete-company', + startedAt: new Date('2026-09-07T12:00:00Z'), + }) + const { hydrate, result } = await runPass({ + access: 'admin', + readCurrent: true, + checkpoint, + existing: { + ...EXISTING, + contentHash: 'old-body', + storageKey: 'kb/old.txt', + sourceSeenAt: new Date(checkpoint.startedAt), + }, + }) + expect(hydrate).not.toHaveBeenCalled() + expect(result.docsUpdated).toBe(0) + }) + it('does not reconcile deletions after a user listing failed, even without the unsafe marker', async () => { sourceBody = { value: '

    Current content

    ' } const checkpoint = { @@ -566,6 +602,89 @@ describe('content pass checkpoint intent', () => { ) }) +describe('permission refresh through the shared content pass', () => { + const current: StoredPage = { + ...EXISTING, + contentHash: 'confluence:storage-local-body-v2:page:3', + storageKey: 'kb/current.txt', + sourceSeenAt: new Date('2026-09-07T12:00:00Z'), + } + + it('refreshes unchanged stored bodies without hydration, new content, counts, or absence reconciliation', async () => { + const { pass, result, hydrate } = await runPass({ + access: 'admin', + existing: current, + permissionsOnly: true, + permissionStoredAfter: current, + }) + expect(hydrate).not.toHaveBeenCalled() + expect(mocks.onPage).toHaveBeenCalledWith( + [expect.objectContaining({ externalId: 'page' })], + expect.any(Date) + ) + expect(pass.checkpoint.listedCount).toBe(0) + expect(result).toMatchObject({ docsAdded: 0, docsUpdated: 0, docsDeleted: 0 }) + expect( + dbChainMockFns.set.mock.calls.some( + ([values]) => 'sourceSeenAt' in values || 'deletedAt' in values + ) + ).toBe(false) + expect(mocks.hardDelete).not.toHaveBeenCalled() + }) + + it('ignores documents not previously stored instead of indexing them during permission refresh', async () => { + const { hydrate, result } = await runPass({ access: 'admin', permissionsOnly: true }) + expect(hydrate).not.toHaveBeenCalled() + expect(mocks.onPage).toHaveBeenCalledWith([], expect.any(Date)) + expect(result.docsAdded).toBe(0) + expect(mocks.upload).not.toHaveBeenCalled() + }) + + it('revokes an old body before repairing a changed document and renews permission only for the persisted version', async () => { + sourceBody = { value: '

    Current safe body

    ' } + const { hydrate, result } = await runPass({ + access: 'admin', + permissionsOnly: true, + readCurrent: true, + existing: { ...current, contentHash: 'old-body' }, + permissionStoredAfter: current, + }) + expect(hydrate).toHaveBeenCalledOnce() + expect(dbChainMockFns.set).toHaveBeenCalledWith({ + acl: [], + aclRequirements: [], + aclVerifiedAt: null, + }) + expect(dbChainMockFns.set.mock.calls.some(([value]) => 'sourceSeenAt' in value)).toBe(false) + expect(result.docsUpdated).toBe(1) + expect(mocks.onPage).toHaveBeenCalledWith( + [expect.objectContaining({ contentHash: current.contentHash })], + expect.any(Date) + ) + }) + + it('never renews a changed body after its hydration fails', async () => { + const { hydrate, result } = await runPass({ + access: 'admin', + permissionsOnly: true, + existing: { ...current, contentHash: 'old-body' }, + getDocument: async () => { + throw new Error('provider unavailable') + }, + permissionStoredAfter: { ...current, contentHash: null }, + }) + expect(hydrate).toHaveBeenCalledOnce() + expect(result.docsFailed).toBe(1) + expect(dbChainMockFns.set).toHaveBeenCalledWith({ + acl: [], + aclRequirements: [], + aclVerifiedAt: null, + }) + expect(dbChainMockFns.set.mock.calls.some(([value]) => 'sourceSeenAt' in value)).toBe(false) + expect(mocks.onPage).toHaveBeenCalledWith([], expect.any(Date)) + }) +}) + describe('Confluence empty content through the shared content pass', () => { it.each(['admin', 'members'] as const)( 'recovers a failed stub, reuses its verified empty version, and indexes an edited page for %s', diff --git a/apps/sim/lib/knowledge/connectors/sync-content-pass.ts b/apps/sim/lib/knowledge/connectors/sync-content-pass.ts index 6fc86b716fb..68b482446d1 100644 --- a/apps/sim/lib/knowledge/connectors/sync-content-pass.ts +++ b/apps/sim/lib/knowledge/connectors/sync-content-pass.ts @@ -3,13 +3,25 @@ import { document, knowledgeConnector } from '@sim/db/schema' import { and, asc, eq, inArray, isNotNull, isNull, lt, type SQL, sql } from 'drizzle-orm' import type { BillingAttributionSnapshot } from '@/lib/billing/core/billing-attribution' import type { DbOrTx } from '@/lib/db/types' -import type { ConnectorAccessMode } from '@/lib/knowledge/connectors/access-modes' +import { + type ConnectorAccessMode, + effectiveConnectorSyncIntervalMinutes, +} from '@/lib/knowledge/connectors/access-modes' +import { + createGoogleCompanyScheduler, + GOOGLE_COMPANY_PERMISSION_REFRESH_MS, + isGoogleCompanySource, +} from '@/lib/knowledge/connectors/google-company-scheduler' import { beginListingCheckpoint, type ListingCheckpoint, readListingCheckpoint, runResumableListing, } from '@/lib/knowledge/connectors/listing-checkpoint' +import { + commitConnectorPartitionWork, + connectorPartitionWorkStore, +} from '@/lib/knowledge/connectors/partition-store' import { SOURCE_CONTENT_ERROR, SOURCE_PERMISSION_ERROR, @@ -33,6 +45,8 @@ import { resolveReconciliationDeleteCap, } from '@/lib/knowledge/connectors/sync-primitives' import { hardDeleteDocuments } from '@/lib/knowledge/documents/service' +import { SIM_SEARCH_SYNC_INTERVAL_MINUTES } from '@/lib/sim-search/constants' +import { googleCompanyUserContextSchema } from '@/connectors/google-workspace/company-work' import type { ConnectorConfig, ExternalDocument, SyncResult } from '@/connectors/types' interface ContentPassInput { @@ -42,6 +56,7 @@ interface ContentPassInput { connectorType: string listingCheckpoint?: unknown lastSyncDocCount?: number | null + syncIntervalMinutes?: number } connectorConfig: ConnectorConfig sourceConfig: Record @@ -102,8 +117,34 @@ export async function runConnectorContentPass(input: ContentPassInput) { }) } let hydratedCount = 0 + const syncIntervalMinutes = effectiveConnectorSyncIntervalMinutes( + input.documentAccess, + input.connector.syncIntervalMinutes ?? SIM_SEARCH_SYNC_INTERVAL_MINUTES + ) + const companyStore = () => + connectorPartitionWorkStore( + input.connectorId, + String(input.syncContext.syncRunId), + googleCompanyUserContextSchema.parse, + syncIntervalMinutes > 0 + ) + const company = isGoogleCompanySource(input.connector.connectorType, input.syncContext) + ? createGoogleCompanyScheduler({ + provider: input.connector.connectorType, + listDocuments: input.connectorConfig.listDocuments, + isListingCursorInvalidError: input.connectorConfig.isListingCursorInvalidError, + syncIntervalMinutes, + store: { + get: (...args) => companyStore().get(...args), + next: (...args) => companyStore().next(...args), + remaining: () => companyStore().remaining(), + }, + }) + : undefined checkpoint = await runResumableListing({ - connectorConfig: input.connectorConfig, + connectorConfig: company + ? { ...input.connectorConfig, listDocuments: company.listDocuments } + : input.connectorConfig, sourceConfig: input.sourceConfig, syncContext: input.syncContext, checkpoint, @@ -111,14 +152,55 @@ export async function runConnectorContentPass(input: ContentPassInput) { beforePage: input.lease.beatIfDue, getGenerationStartedAt: () => withLease(readGenerationStartedAt), getAccessToken: input.getAccessToken, - processPage: async (documents, cycle) => { + processPage: async (documents, cycle, page) => { + const corpus = await loadPageCorpus( + input.connectorId, + documents.map((item) => item.externalId) + ) + if (page.permissionsOnly) + documents = documents.filter((item) => corpus.priorByExternalId.has(item.externalId)) const externalIds = documents.map((item) => item.externalId) - const corpus = await loadPageCorpus(input.connectorId, externalIds) + if (page.permissionsOnly) { + const changed = documents.filter((item) => { + const prior = corpus.priorByExternalId.get(item.externalId) + return ( + prior && + (!prior.contentHash || + prior.contentHash !== item.contentHash || + prior.storageKey === null) + ) + }) + /** Revoke grants without matching stored content, retaining the content crawl's observation for EOF reconciliation. */ + if (changed.length) + await withLease(async (tx) => { + for (let offset = 0; offset < changed.length; offset += 500) { + await tx + .update(document) + .set({ acl: [], aclRequirements: [], aclVerifiedAt: null }) + .where( + and( + eq(document.connectorId, input.connectorId), + inArray( + document.externalId, + changed.slice(offset, offset + 500).map((item) => item.externalId) + ), + isNull(document.archivedAt) + ) + ) + } + }) + } const state = createSyncRunState(input.result) const startedAt = new Date(cycle.startedAt) const remaining = documents.filter((item) => { const prior = corpus.priorByExternalId.get(item.externalId) - if (!prior?.sourceSeenAt || prior.sourceSeenAt < startedAt) return true + if (page.permissionsOnly) return prior?.contentHash !== item.contentHash + if ( + !prior?.sourceSeenAt || + prior.sourceSeenAt < startedAt || + (company && prior.contentHash !== null && prior.contentHash !== item.contentHash) + ) + return true /** A crash after persisting a failed batch must not erase its failure evidence. */ if (prior.contentHash === null && !corpus.excludedExternalIds.has(item.externalId)) { cycle.contentFailures = true @@ -142,6 +224,7 @@ export async function runConnectorContentPass(input: ContentPassInput) { }) cycle.contentFailures = true } + if (page.permissionsOnly) return const attemptedIds = attempted.map((item) => item.externalId) await withLease(async (tx) => { for (let offset = 0; offset < attemptedIds.length; offset += 500) { @@ -161,7 +244,7 @@ export async function runConnectorContentPass(input: ContentPassInput) { const pendingOps = classifyListing({ externalDocs: remaining, corpus, - forceRehydrate: cycle.forceRehydrate, + forceRehydrate: !page.permissionsOnly && cycle.forceRehydrate, state, }) const pendingIds = new Set(pendingOps.map((op) => op.extDoc.externalId)) @@ -171,15 +254,32 @@ export async function runConnectorContentPass(input: ContentPassInput) { corpus, state, pendingOps, - forceRehydrate: cycle.forceRehydrate, + forceRehydrate: !page.permissionsOnly && cycle.forceRehydrate, onBatchComplete: async (attempted) => { hydratedCount += attempted.filter((item) => item.contentDeferred).length await persistAttempted(attempted) }, }) if (!finished) return false - const pageOutcome = await input.onPage?.(documents, startedAt) + const permissionCorpus = page.permissionsOnly + ? await loadPageCorpus(input.connectorId, externalIds) + : undefined + const verifiedDocuments = documents.filter((item) => { + if (!permissionCorpus) return true + if (state.failedExternalIds.has(item.externalId)) return false + const stored = permissionCorpus.priorByExternalId.get(item.externalId) + return Boolean( + stored?.contentHash && + stored.contentHash === item.contentHash && + stored.storageKey !== null + ) + }) + const pageOutcome = await input.onPage?.( + verifiedDocuments, + page.permissionsOnly ? (company?.permissionStartedAt() ?? startedAt) : startedAt + ) if (pageOutcome?.permissionsIncomplete) cycle.permissionFailures = true + if (page.permissionsOnly) return await withLease(async (tx) => { const verified = externalIds.filter((id) => !state.failedExternalIds.has(id)) for (let offset = 0; offset < verified.length; offset += 500) { @@ -197,13 +297,24 @@ export async function runConnectorContentPass(input: ContentPassInput) { } }) }, - saveCheckpoint: (next) => - withLease((tx) => - tx + saveCheckpoint: async (next) => { + await withLease(async (tx) => { + const changes = company?.changesFor(next.cursor) + if (changes) + await commitConnectorPartitionWork( + tx, + input.connectorId, + next.generationId, + changes, + new Date(new Date(next.startedAt).getTime() + GOOGLE_COMPANY_PERMISSION_REFRESH_MS) + ) + await tx .update(knowledgeConnector) .set({ listingCheckpoint: next }) .where(input.lease.stillHeld()) - ).then(() => undefined), + }) + company?.didCommit(next.cursor) + }, }) const reconciliation = checkpoint.complete ? await reconcileCompletedListing(input, checkpoint, withLease) diff --git a/apps/sim/lib/knowledge/connectors/sync-deferral.ts b/apps/sim/lib/knowledge/connectors/sync-deferral.ts index 1eef01466de..0d1e497b1c9 100644 --- a/apps/sim/lib/knowledge/connectors/sync-deferral.ts +++ b/apps/sim/lib/knowledge/connectors/sync-deferral.ts @@ -53,7 +53,7 @@ export async function deferConnectorSync(input: { .select({ id: knowledgeBase.id }) .from(knowledgeBase) .where(and(eq(knowledgeBase.id, input.knowledgeBaseId), isNull(knowledgeBase.deletedAt))) - .for('update') + .for('share') if (!liveKnowledgeBase) throw new SyncLockLostException(input.connectorId) const [held] = await tx .select({ id: knowledgeConnector.id }) diff --git a/apps/sim/lib/knowledge/connectors/sync-engine.test.ts b/apps/sim/lib/knowledge/connectors/sync-engine.test.ts index 29ad7d9d714..ae0efed77df 100644 --- a/apps/sim/lib/knowledge/connectors/sync-engine.test.ts +++ b/apps/sim/lib/knowledge/connectors/sync-engine.test.ts @@ -3422,7 +3422,7 @@ describe('executeSync terminal exits under a lost lock', () => { expect(dbChainMockFns.set).not.toHaveBeenCalledWith( expect.objectContaining({ status: 'active', consecutiveFailures: 0 }) ) - expect(dbChainMockFns.for).toHaveBeenCalledWith('update') + expect(dbChainMockFns.for).toHaveBeenCalledWith('share') }) it.each(['checkpoint', 'batch'] as const)( diff --git a/apps/sim/lib/knowledge/connectors/sync-engine.ts b/apps/sim/lib/knowledge/connectors/sync-engine.ts index 5632d2e6a43..c39b5524d43 100644 --- a/apps/sim/lib/knowledge/connectors/sync-engine.ts +++ b/apps/sim/lib/knowledge/connectors/sync-engine.ts @@ -305,6 +305,7 @@ export interface ContentPassOutcome { startedAt: string listedCount: number incrementalSince?: string | null + resumeAt?: string | null } } @@ -371,7 +372,7 @@ export async function completeSuccessfulSync( .select({ id: knowledgeBase.id }) .from(knowledgeBase) .where(and(eq(knowledgeBase.id, knowledgeBaseId), isNull(knowledgeBase.deletedAt))) - .for('update') + .for('share') if (!lockedKnowledgeBase) throw new SyncCompletionOwnershipLost() const [lockedConnector] = await tx @@ -447,7 +448,11 @@ export async function completeSuccessfulSync( ...buildSyncSuccessUpdate( now, actualDocCount, - contentPass && !contentPass.complete ? now : calculateNextSyncTime(syncIntervalMinutes), + contentPass && !contentPass.complete + ? contentPass.checkpoint.resumeAt + ? new Date(contentPass.checkpoint.resumeAt) + : now + : calculateNextSyncTime(syncIntervalMinutes), completionNotice, result.docsFailed === 0 && (!contentPass || !isContentPassIncomplete(contentPass)) ), diff --git a/apps/sim/lib/knowledge/connectors/sync-primitives.ts b/apps/sim/lib/knowledge/connectors/sync-primitives.ts index 5f20e765fca..562378d26b0 100644 --- a/apps/sim/lib/knowledge/connectors/sync-primitives.ts +++ b/apps/sim/lib/knowledge/connectors/sync-primitives.ts @@ -1384,7 +1384,7 @@ export async function sweepStuckDocuments(input: SweepStuckDocumentsInput): Prom .select({ id: knowledgeBase.id }) .from(knowledgeBase) .where(and(eq(knowledgeBase.id, knowledgeBaseId), isNull(knowledgeBase.deletedAt))) - .for('update') + .for('share') if (!activeKnowledgeBase) throw new SyncLockLostException(connectorId) const [heldSyncLock] = await tx diff --git a/apps/sim/lib/knowledge/documents/ocr-source-validation.test.ts b/apps/sim/lib/knowledge/documents/ocr-source-validation.test.ts index c6131154e15..9940e21f06a 100644 --- a/apps/sim/lib/knowledge/documents/ocr-source-validation.test.ts +++ b/apps/sim/lib/knowledge/documents/ocr-source-validation.test.ts @@ -3,8 +3,70 @@ import { describe, expect, it } from 'vitest' import { assertOcrSourceSupported } from '@/lib/knowledge/documents/ocr-source-validation' const GIF = Buffer.from('R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==', 'base64') +const LFS_POINTER = `version https://git-lfs.github.com/spec/v1\noid sha256:${'a'.repeat(64)}\nsize 9566\n` describe('OCR source preflight', () => { + it.each(['image/png', 'image/jpeg', 'image/gif', 'application/pdf'])( + 'reports missing Git LFS content for %s without classifying it as a provider failure', + (mimeType) => { + expect(() => assertOcrSourceSupported(Buffer.from(LFS_POINTER), mimeType)).toThrow( + expect.objectContaining({ + name: 'PermanentDocumentProcessingError', + code: 'invalid_file', + message: expect.stringContaining('Git LFS pointer'), + }) + ) + } + ) + it('recognizes a pointer exported with Windows line endings', () => { + expect(() => + assertOcrSourceSupported(Buffer.from(LFS_POINTER.replaceAll('\n', '\r\n')), 'image/png') + ).toThrow(expect.objectContaining({ code: 'invalid_file' })) + }) + it.each(['\n', '\r\n'])('recognizes extension records with %j line endings', (newline) => { + const pointer = LFS_POINTER.replace( + 'oid sha256:', + `ext-0-compress sha256:${'b'.repeat(64)}\next-1-encrypt sha256:${'c'.repeat(64)}\noid sha256:` + ).replaceAll('\n', newline) + expect(() => assertOcrSourceSupported(Buffer.from(pointer), 'image/png')).toThrow( + expect.objectContaining({ + code: 'invalid_file', + message: expect.stringContaining('Git LFS pointer'), + }) + ) + expect(() => + assertOcrSourceSupported(Buffer.from(`${pointer}arbitrary trailing content`), 'image/png') + ).not.toThrow() + expect(() => + assertOcrSourceSupported( + Buffer.from(pointer.replace('ext-0-compress sha256:', 'ext-0-compress sha1:')), + 'image/png' + ) + ).not.toThrow() + }) + it('preserves small images and files containing a pointer as text', () => { + const png = Buffer.from( + 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+/l9sAAAAASUVORK5CYII=', + 'base64' + ) + expect(png.length).toBeLessThan(Buffer.byteLength(LFS_POINTER)) + expect(() => assertOcrSourceSupported(png, 'image/png')).not.toThrow() + expect(() => assertOcrSourceSupported(Buffer.from(LFS_POINTER), 'text/plain')).not.toThrow() + expect(() => + assertOcrSourceSupported(Buffer.concat([png, Buffer.from(LFS_POINTER)]), 'image/png') + ).not.toThrow() + }) + it('requires a complete pointer rather than matching a version URL alone', () => { + for (const content of [ + 'version https://git-lfs.github.com/spec/v1\n', + LFS_POINTER.replace('sha256:', 'sha1:'), + LFS_POINTER.replace('9566', 'unknown'), + `${LFS_POINTER}additional content`, + `${LFS_POINTER}${'x'.repeat(1024)}`, + ]) { + expect(() => assertOcrSourceSupported(Buffer.from(content), 'image/png')).not.toThrow() + } + }) it('accepts static GIFs without decoding their pixels', () => { expect(() => assertOcrSourceSupported(GIF, 'image/gif')).not.toThrow() }) diff --git a/apps/sim/lib/knowledge/documents/ocr-source-validation.ts b/apps/sim/lib/knowledge/documents/ocr-source-validation.ts index 835c3c199e0..e2308f7c1c3 100644 --- a/apps/sim/lib/knowledge/documents/ocr-source-validation.ts +++ b/apps/sim/lib/knowledge/documents/ocr-source-validation.ts @@ -1,5 +1,10 @@ import { PermanentDocumentProcessingError } from '@/lib/knowledge/documents/document-processing-error' +/** Canonical Git LFS pointers are text records smaller than 1 KiB, not the referenced binary. */ +const GIT_LFS_POINTER_MAX_BYTES = 1024 +const GIT_LFS_POINTER_PATTERN = + /^version https:\/\/git-lfs\.github\.com\/spec\/v1\r?\n(?:ext-[0-9]+-[a-z0-9.-]+ sha256:[a-f0-9]{64}\r?\n)*oid sha256:[a-f0-9]{64}\r?\nsize [0-9]+\r?\n$/ + function invalidSource(message: string): never { throw new PermanentDocumentProcessingError('invalid_file', message) } @@ -61,6 +66,15 @@ function assertStaticGif(buffer: Buffer): void { /** Rejects proven input failures before they consume provider admission or paid OCR. */ export function assertOcrSourceSupported(buffer: Buffer, mimeType: string): void { + if ( + (mimeType === 'application/pdf' || mimeType.startsWith('image/')) && + buffer.length < GIT_LFS_POINTER_MAX_BYTES && + GIT_LFS_POINTER_PATTERN.test(buffer.toString('utf8')) + ) { + invalidSource( + 'This file contains a Git LFS pointer instead of the actual PDF or image. Upload the original file content and retry.' + ) + } if (mimeType === 'application/pdf' && !buffer.subarray(0, 1024).includes(Buffer.from('%PDF-'))) { invalidSource('This file is not a valid PDF. Re-export it as a PDF and retry.') } diff --git a/apps/sim/lib/knowledge/documents/pdf-ocr-triage.test.ts b/apps/sim/lib/knowledge/documents/pdf-ocr-triage.test.ts index 723bf3bc6f3..11fa8e213b8 100644 --- a/apps/sim/lib/knowledge/documents/pdf-ocr-triage.test.ts +++ b/apps/sim/lib/knowledge/documents/pdf-ocr-triage.test.ts @@ -43,6 +43,7 @@ vi.mock('@/lib/internal/mistral/operations', () => ({ })) import { env } from '@/lib/core/config/env' +import { waitForProviderAdmission } from '@/lib/core/rate-limiter/provider-admission' import { ProviderCapacityDeferredError } from '@/lib/core/rate-limiter/provider-capacity-error' import { FileParserError } from '@/lib/file-parsers/errors' import { MistralOperationError } from '@/lib/internal/mistral/errors' @@ -206,6 +207,39 @@ describe('PDF OCR triage', () => { } ) + it.each(['mistral', 'azure-mistral'])( + 'rejects a Git LFS pointer before %s admission or OCR', + async (provider) => { + Object.assign(env, { + OCR_PROVIDER: provider, + OCR_AZURE_API_KEY: 'key', + OCR_AZURE_ENDPOINT: 'https://example.openai.azure.com', + OCR_AZURE_MODEL_NAME: 'mistral-document-ai-2512', + }) + const bytes = Buffer.from( + `version https://git-lfs.github.com/spec/v1\noid sha256:${'b'.repeat(64)}\nsize 9566\n` + ) + mockDownload.mockResolvedValue(bytes) + const fetchMock = vi.fn() + vi.stubGlobal('fetch', fetchMock) + + await expect( + processDocument('https://example.com/image.png', 'image.png', 'image/png', 1024, 0, 1, { + userId: 'user-1', + }) + ).rejects.toMatchObject({ + name: 'PermanentDocumentProcessingError', + code: 'invalid_file', + message: expect.stringContaining('Git LFS pointer'), + }) + expect(mockDownload).toHaveBeenCalledOnce() + expect(waitForProviderAdmission).not.toHaveBeenCalled() + expect(mockExecuteMistralParse).not.toHaveBeenCalled() + expect(fetchMock).not.toHaveBeenCalled() + expect(mockParseBuffer).not.toHaveBeenCalled() + } + ) + it('uses the Azure image_url envelope for an image source', async () => { Object.assign(env, { OCR_PROVIDER: 'azure-mistral', diff --git a/apps/sim/lib/knowledge/documents/processing-recovery.ts b/apps/sim/lib/knowledge/documents/processing-recovery.ts index c9aeb3206ed..4a0a457d2ac 100644 --- a/apps/sim/lib/knowledge/documents/processing-recovery.ts +++ b/apps/sim/lib/knowledge/documents/processing-recovery.ts @@ -2,7 +2,7 @@ import { db } from '@sim/db' import { document, knowledgeBase, knowledgeConnector } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { generateId } from '@sim/utils/id' -import { and, asc, eq, inArray, isNull, sql } from 'drizzle-orm' +import { and, asc, eq, inArray, isNull, notInArray, sql } from 'drizzle-orm' import { assertBillingAttributionOwner, resolveSystemBillingAttribution, @@ -23,6 +23,7 @@ const logger = createLogger('KnowledgeDocumentRecovery') export const KNOWLEDGE_DOCUMENT_RECOVERY_OUTBOX_EVENT = 'knowledge.document.processing.recover' export const DOCUMENT_RECOVERY_BATCH_SIZE = 200 const RECOVERY_RUNTIME_MS = 20_000 +const MAX_RECOVERY_CANDIDATE_BATCHES = 4 const RECOVERABLE_CONNECTOR_STATUSES = ['active', 'error', 'pending', 'syncing'] /** @@ -40,6 +41,34 @@ async function recoverStoredDocuments( deadlineAt: number, signal: AbortSignal ): Promise { + let recovered = 0 + const blockedKnowledgeBases = new Set() + const attemptedConnectors = new Set() + for (let batch = 0; batch < MAX_RECOVERY_CANDIDATE_BATCHES; batch++) { + if (recovered >= DOCUMENT_RECOVERY_BATCH_SIZE || Date.now() >= deadlineAt) break + const connectorsBefore = attemptedConnectors.size + recovered += await recoverStoredDocumentBatch( + now, + deadlineAt, + signal, + blockedKnowledgeBases, + attemptedConnectors, + DOCUMENT_RECOVERY_BATCH_SIZE - recovered + ) + if (attemptedConnectors.size === connectorsBefore) break + } + return recovered +} + +async function recoverStoredDocumentBatch( + now: Date, + deadlineAt: number, + signal: AbortSignal, + blockedKnowledgeBases: Set, + attemptedConnectors: Set, + limit: number +): Promise { + /** Discovery does not claim work. Ownership is rechecked under lifecycle locks below. */ const candidates = await db.transaction(async (tx) => { signal.throwIfAborted() await tx.execute( @@ -50,7 +79,7 @@ async function recoverStoredDocuments( .select({ id: document.id, knowledgeBaseId: document.knowledgeBaseId, - connectorId: document.connectorId, + connectorId: knowledgeConnector.id, workspaceId: knowledgeBase.workspaceId, organizationId: knowledgeBase.organizationId, }) @@ -60,6 +89,12 @@ async function recoverStoredDocuments( .where( and( documentProcessingRecoveryCondition(now), + blockedKnowledgeBases.size > 0 + ? notInArray(document.knowledgeBaseId, [...blockedKnowledgeBases]) + : undefined, + attemptedConnectors.size > 0 + ? notInArray(document.connectorId, [...attemptedConnectors]) + : undefined, isNull(knowledgeBase.deletedAt), isNull(knowledgeConnector.deletedAt), isNull(knowledgeConnector.archivedAt), @@ -67,10 +102,10 @@ async function recoverStoredDocuments( ) ) .orderBy(asc(document.uploadedAt), asc(document.id)) - .limit(DOCUMENT_RECOVERY_BATCH_SIZE) - .for('update', { of: knowledgeBase, skipLocked: true }) + .limit(limit) }) signal.throwIfAborted() + if (candidates.length === 0) return 0 let recovered = 0 const groups = new Map() @@ -81,7 +116,10 @@ async function recoverStoredDocuments( } for (const [knowledgeBaseId, group] of groups) { if (Date.now() >= deadlineAt) break + const connectorIds = [...new Set(group.map((row) => row.connectorId))] + for (const connectorId of connectorIds) attemptedConnectors.add(connectorId) const owner = group[0] + let ownerVerified = false try { signal.throwIfAborted() const attribution = owner.organizationId @@ -107,18 +145,20 @@ async function recoverStoredDocuments( }) .from(knowledgeBase) .where(and(eq(knowledgeBase.id, knowledgeBaseId), isNull(knowledgeBase.deletedAt))) - .for('update', { skipLocked: true }) + .for('share', { skipLocked: true }) signal.throwIfAborted() - if (!kb) return 0 + if (!kb) { + blockedKnowledgeBases.add(knowledgeBaseId) + return 0 + } assertBillingAttributionOwner(attribution, kb) + ownerVerified = true const connectors = await tx .select({ id: knowledgeConnector.id }) .from(knowledgeConnector) .where( and( - inArray(knowledgeConnector.id, [ - ...new Set(group.flatMap((row) => (row.connectorId ? [row.connectorId] : []))), - ]), + inArray(knowledgeConnector.id, connectorIds), eq(knowledgeConnector.knowledgeBaseId, knowledgeBaseId), inArray(knowledgeConnector.status, RECOVERABLE_CONNECTOR_STATUSES), isNull(knowledgeConnector.archivedAt), @@ -203,6 +243,7 @@ async function recoverStoredDocuments( }) } catch (error) { signal.throwIfAborted() + if (!ownerVerified) blockedKnowledgeBases.add(knowledgeBaseId) logger.error('Stored document recovery admission failed', { knowledgeBaseId, diagnostic: getConnectorFailureDiagnostic(error), diff --git a/apps/sim/lib/oauth/credential-service.test.ts b/apps/sim/lib/oauth/credential-service.test.ts index a9f10ec759f..cd388def9c3 100644 --- a/apps/sim/lib/oauth/credential-service.test.ts +++ b/apps/sim/lib/oauth/credential-service.test.ts @@ -80,6 +80,7 @@ import { getServiceAccountToken, refreshTokenIfNeeded, resolveCredentialTokenBundle, + resolveServiceAccountToken, ServiceAccountTokenError, } from '@/lib/oauth/credential-service' import { GOOGLE_SERVICE_ACCOUNT_PROVIDER_ID } from '@/lib/oauth/types' @@ -414,6 +415,7 @@ describe('Google service-account token minting', () => { redirect: 'error', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: expect.any(URLSearchParams), + signal: expect.any(AbortSignal), }) const body = fetchMock.mock.calls[0][1]?.body expect(body).toBeInstanceOf(URLSearchParams) @@ -455,6 +457,23 @@ describe('Google service-account token minting', () => { expect(fetchMock).toHaveBeenCalledTimes(1) }) + it('preserves cancellation through the service-account resolver registry', async () => { + const controller = new AbortController() + const reason = new DOMException('Caller cancelled', 'AbortError') + controller.abort(reason) + await expect( + resolveServiceAccountToken( + 'credential-1', + GOOGLE_SERVICE_ACCOUNT_PROVIDER_ID, + [driveScope], + 'member@example.com', + { signal: controller.signal } + ) + ).rejects.toBe(reason) + expect(mocks.decryptSecret).not.toHaveBeenCalled() + expect(fetchMock).not.toHaveBeenCalled() + }) + it('retains the Google error code for actionable setup failures', async () => { queueTableRows(credential, [row]) fetchMock.mockResolvedValueOnce( @@ -474,6 +493,13 @@ describe('Google service-account token minting', () => { errorCode: 'unauthorized_client', errorDescription: RAW_PROVIDER_ERROR, }) + expect(mocks.logger.error).toHaveBeenCalledWith('Service account token exchange failed', { + status: 401, + subject: 'admin@example.com', + scopes: [driveScope], + errorCode: 'unauthorized_client', + }) + expect(JSON.stringify(mocks.logger.error.mock.calls)).not.toContain(RAW_PROVIDER_ERROR) }) it('keeps token errors private for selectors', async () => { @@ -503,11 +529,11 @@ describe('Google service-account token minting', () => { '{"error_description":""}', ])('handles malformed provider errors without losing the HTTP status: %s', async (body) => { queueTableRows(credential, [row]) - fetchMock.mockResolvedValueOnce(new Response(body, { status: 503 })) + fetchMock.mockResolvedValueOnce(new Response(body, { status: 400 })) await expect(getServiceAccountToken('credential-1', [driveScope])).rejects.toMatchObject({ - statusCode: 503, + statusCode: 400, errorCode: undefined, - errorDescription: 'Token exchange failed: 503', + errorDescription: 'Token exchange failed: 400', }) }) @@ -525,4 +551,47 @@ describe('Google service-account token minting', () => { errorDescription: 'Invalid account credentials.', }) }) + + it('returns the existing typed error after bounded transient retries and keeps selectors private', async () => { + queueTableRows(credential, [row]) + fetchMock.mockImplementation(async () => + Response.json( + { + error: 'temporarily_unavailable', + error_description: RAW_PROVIDER_ERROR, + }, + { status: 503 } + ) + ) + const request = getServiceAccountToken('credential-1', [driveScope], 'private@example.com', { + privacyMode: 'selector', + }) + const checked = expect(request).rejects.toMatchObject({ + name: 'ServiceAccountTokenError', + statusCode: 503, + errorCode: undefined, + errorDescription: 'Token exchange failed: 503', + }) + await vi.runAllTimersAsync() + await checked + expect(fetchMock).toHaveBeenCalledTimes(3) + const logs = JSON.stringify([ + ...mocks.logger.info.mock.calls, + ...mocks.logger.warn.mock.calls, + ...mocks.logger.error.mock.calls, + ]) + expect(logs).not.toContain(RAW_PROVIDER_ERROR) + expect(logs).not.toContain('private@example.com') + expect(logs).not.toContain('crawler@qa-project.iam.gserviceaccount.com') + }) + + it('retains the HTTP status when an error payload exceeds the response limit', async () => { + queueTableRows(credential, [row]) + fetchMock.mockResolvedValueOnce(new Response('x'.repeat(100_000), { status: 400 })) + await expect(getServiceAccountToken('credential-1', [driveScope])).rejects.toMatchObject({ + statusCode: 400, + errorDescription: 'Token exchange failed: 400', + }) + expect(JSON.stringify(mocks.logger.error.mock.calls)).not.toContain('xxxx') + }) }) diff --git a/apps/sim/lib/oauth/credential-service.ts b/apps/sim/lib/oauth/credential-service.ts index 7cfee32eaea..ebfb51a8118 100644 --- a/apps/sim/lib/oauth/credential-service.ts +++ b/apps/sim/lib/oauth/credential-service.ts @@ -29,6 +29,7 @@ import { GITHUB_INSTALLATION_PROVIDER_ID, type GitHubInstallationRepositoryScope, } from '@/lib/oauth/github-installation-types' +import { exchangeGoogleServiceAccountJwt } from '@/lib/oauth/google-service-account-transport' import { isInstagramProvider, shouldProactivelyRefreshInstagramToken } from '@/lib/oauth/instagram' import { getMicrosoftRefreshTokenExpiry, @@ -73,6 +74,8 @@ export interface CredentialTokenResolutionOptions { privacyMode?: 'selector' /** GitHub installation content tokens may only address one connector repository. */ githubRepositoryScope?: GitHubInstallationRepositoryScope + /** Cancels Google service-account token exchange and retry waits. */ + signal?: AbortSignal } function privateCredentialIdentity(namespace: string, value: string): string { @@ -189,6 +192,19 @@ const SA_EXCLUDED_SCOPES = new Set([ 'https://www.googleapis.com/auth/userinfo.profile', ]) +/** Google's documented JWT error codes are safe to retain without the provider description. */ +const SA_DIAGNOSTIC_ERROR_CODES = new Set([ + 'access_denied', + 'admin_policy_enforced', + 'deleted_client', + 'disabled_client', + 'invalid_client', + 'invalid_grant', + 'invalid_scope', + 'org_internal', + 'unauthorized_client', +]) + /** * Generates a short-lived access token for a Google service account credential * using the two-legged OAuth JWT flow (RFC 7523). @@ -204,6 +220,7 @@ export async function getServiceAccountToken( impersonateEmail?: string, options?: CredentialTokenResolutionOptions ): Promise { + options?.signal?.throwIfAborted() const [credentialRow] = await db .select({ type: credential.type, @@ -267,6 +284,7 @@ export async function getServiceAccountToken( hasSubject: Boolean(impersonateEmail), scopes: filteredScopes.join(' '), aud: tokenUri, + subject: impersonateEmail, } ) @@ -280,22 +298,10 @@ export async function getServiceAccountToken( const jwt = `${signingInput}.${signature}` - const response = await fetch(tokenUri, { - method: 'POST', - redirect: 'error', - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, - body: new URLSearchParams({ - grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer', - assertion: jwt, - }), - }) + const response = await exchangeGoogleServiceAccountJwt(tokenUri, jwt, options?.signal) if (!response.ok) { - const errorBody = await response.text() - logger.error('Service account token exchange failed', { - status: response.status, - ...(options?.privacyMode === 'selector' ? {} : { body: errorBody }), - }) + const errorBody = response.body let description = `Token exchange failed: ${response.status}` let errorCode: string | undefined if (options?.privacyMode !== 'selector') { @@ -322,10 +328,20 @@ export async function getServiceAccountToken( /** Retain the status-based description when Google returns a non-JSON error. */ } } + logger.error('Service account token exchange failed', { + status: response.status, + ...(options?.privacyMode === 'selector' + ? {} + : { + subject: impersonateEmail, + scopes: filteredScopes, + ...(errorCode && SA_DIAGNOSTIC_ERROR_CODES.has(errorCode) ? { errorCode } : {}), + }), + }) throw new ServiceAccountTokenError(response.status, description, errorCode) } - const tokenData = (await response.json()) as { access_token: string } + const tokenData = JSON.parse(response.body) as { access_token: string } return tokenData.access_token } @@ -660,11 +676,9 @@ async function resolveClientCredentialAccountToken( }) } -interface ServiceAccountTokenOptions { +interface ServiceAccountTokenOptions extends CredentialTokenResolutionOptions { scopes?: string[] impersonateEmail?: string - privacyMode?: 'selector' - githubRepositoryScope?: GitHubInstallationRepositoryScope } type ServiceAccountTokenResolver = ( @@ -725,7 +739,7 @@ const SERVICE_ACCOUNT_TOKEN_RESOLVERS: Record { if (!scopes?.length) { throw new Error('Scopes are required for service account credentials') @@ -733,6 +747,7 @@ const SERVICE_ACCOUNT_TOKEN_RESOLVERS: Record() + +beforeEach(() => { + vi.useFakeTimers() + fetchMock.mockReset() + vi.stubGlobal('fetch', fetchMock) +}) + +afterEach(() => { + vi.unstubAllGlobals() + vi.useRealTimers() +}) + +describe('Google service-account token transport', () => { + it.each([429, 500, 502, 503, 504])( + 'recovers a transient HTTP %s without changing the assertion', + async (status) => { + const first = Response.json({ error: 'temporarily_unavailable' }, { status }) + fetchMock + .mockResolvedValueOnce(first) + .mockResolvedValueOnce(Response.json({ access_token: 'token' })) + const request = exchangeGoogleServiceAccountJwt(TOKEN_URI, ASSERTION) + const checked = expect(request).resolves.toMatchObject({ status: 200 }) + await vi.runAllTimersAsync() + await checked + expect(fetchMock).toHaveBeenCalledTimes(2) + expect(first.bodyUsed).toBe(true) + for (const [url, init] of fetchMock.mock.calls) { + expect(url).toBe(TOKEN_URI) + expect(init?.signal).toBeInstanceOf(AbortSignal) + expect(init?.redirect).toBe('error') + expect((init?.body as URLSearchParams).get('assertion')).toBe(ASSERTION) + } + } + ) + + it.each([400, 401, 403])('returns permanent HTTP %s without retrying', async (status) => { + const response = Response.json({ error: 'invalid_grant' }, { status }) + fetchMock.mockResolvedValueOnce(response) + await expect(exchangeGoogleServiceAccountJwt(TOKEN_URI, ASSERTION)).resolves.toEqual({ + ok: false, + status, + body: '{"error":"invalid_grant"}', + }) + expect(fetchMock).toHaveBeenCalledTimes(1) + expect(response.bodyUsed).toBe(true) + }) + + it('bounds repeated failures to three attempts and preserves the final error response', async () => { + fetchMock.mockImplementation(async () => + Response.json({ error: 'temporarily_unavailable' }, { status: 503 }) + ) + const request = exchangeGoogleServiceAccountJwt(TOKEN_URI, ASSERTION) + await vi.runAllTimersAsync() + const response = await request + expect(fetchMock).toHaveBeenCalledTimes(3) + expect(response.status).toBe(503) + expect(JSON.parse(response.body)).toEqual({ error: 'temporarily_unavailable' }) + }) + + it('retries a network reset and preserves permanent transport failures', async () => { + const reset = new TypeError('fetch failed', { + cause: Object.assign(new Error('socket'), { code: 'ECONNRESET' }), + }) + fetchMock + .mockRejectedValueOnce(reset) + .mockResolvedValueOnce(Response.json({ access_token: 'token' })) + const request = exchangeGoogleServiceAccountJwt(TOKEN_URI, ASSERTION) + const checked = expect(request).resolves.toMatchObject({ status: 200 }) + await vi.runAllTimersAsync() + await checked + expect(fetchMock).toHaveBeenCalledTimes(2) + fetchMock.mockReset() + const rejected = new TypeError('Invalid URL') + fetchMock.mockRejectedValueOnce(rejected) + await expect(exchangeGoogleServiceAccountJwt(TOKEN_URI, ASSERTION)).rejects.toBe(rejected) + expect(fetchMock).toHaveBeenCalledTimes(1) + }) + + it('honors Retry-After and does not shorten waits exceeding the total budget', async () => { + fetchMock + .mockResolvedValueOnce(new Response(null, { status: 429, headers: { 'Retry-After': '5' } })) + .mockResolvedValueOnce(Response.json({ access_token: 'token' })) + const request = exchangeGoogleServiceAccountJwt(TOKEN_URI, ASSERTION) + await vi.advanceTimersByTimeAsync(4999) + expect(fetchMock).toHaveBeenCalledTimes(1) + await vi.advanceTimersByTimeAsync(1) + await expect(request).resolves.toMatchObject({ status: 200 }) + fetchMock.mockReset() + const response = new Response(null, { status: 429, headers: { 'Retry-After': '60' } }) + fetchMock.mockResolvedValueOnce(response) + await expect(exchangeGoogleServiceAccountJwt(TOKEN_URI, ASSERTION)).resolves.toEqual({ + ok: false, + status: 429, + body: '', + }) + expect(fetchMock).toHaveBeenCalledTimes(1) + }) + + it('preserves caller cancellation before requests and during retry waits', async () => { + const controller = new AbortController() + const reason = new DOMException('Cancelled', 'AbortError') + controller.abort(reason) + await expect( + exchangeGoogleServiceAccountJwt(TOKEN_URI, ASSERTION, controller.signal) + ).rejects.toBe(reason) + expect(fetchMock).not.toHaveBeenCalled() + const waiting = new AbortController() + fetchMock.mockResolvedValueOnce(new Response(null, { status: 503 })) + const request = exchangeGoogleServiceAccountJwt(TOKEN_URI, ASSERTION, waiting.signal) + const checked = expect(request).rejects.toBe(reason) + await vi.advanceTimersByTimeAsync(1) + waiting.abort(reason) + await checked + expect(fetchMock).toHaveBeenCalledTimes(1) + }) + + it('aborts a hung request when its total budget expires', async () => { + let requestSignal: AbortSignal | null | undefined + fetchMock.mockImplementation((_url, init) => { + requestSignal = init?.signal + return new Promise(() => undefined) + }) + const request = exchangeGoogleServiceAccountJwt(TOKEN_URI, ASSERTION) + const checked = expect(request).rejects.toMatchObject({ name: 'TimeoutError' }) + await vi.advanceTimersByTimeAsync(30_000) + await checked + expect(requestSignal?.aborted).toBe(true) + expect(fetchMock).toHaveBeenCalledTimes(1) + }) + + it.each([200, 400, 503])( + 'bounds a stalled HTTP %s response body within the same deadline', + async (status) => { + let requestSignal: AbortSignal | null | undefined + fetchMock.mockImplementation(async (_url, init) => { + requestSignal = init?.signal + return new Response( + new ReadableStream({ + start(controller) { + requestSignal?.addEventListener( + 'abort', + () => controller.error(requestSignal?.reason), + { once: true } + ) + }, + }), + { status } + ) + }) + const request = exchangeGoogleServiceAccountJwt(TOKEN_URI, ASSERTION) + const checked = expect(request).rejects.toMatchObject({ name: 'TimeoutError' }) + await vi.advanceTimersByTimeAsync(30_000) + await checked + expect(requestSignal?.aborted).toBe(true) + expect(fetchMock).toHaveBeenCalledTimes(1) + } + ) + + it('preserves the exact caller abort while reading an error body', async () => { + const caller = new AbortController() + const reason = new DOMException('Caller cancelled', 'AbortError') + fetchMock.mockImplementation( + async (_url, init) => + new Response( + new ReadableStream({ + start(controller) { + init?.signal?.addEventListener( + 'abort', + () => controller.error(new Error('body closed')), + { once: true } + ) + }, + }), + { status: 400 } + ) + ) + const request = exchangeGoogleServiceAccountJwt(TOKEN_URI, ASSERTION, caller.signal) + const checked = expect(request).rejects.toBe(reason) + await vi.advanceTimersByTimeAsync(1) + caller.abort(reason) + await checked + expect(fetchMock).toHaveBeenCalledTimes(1) + }) +}) diff --git a/apps/sim/lib/oauth/google-service-account-transport.ts b/apps/sim/lib/oauth/google-service-account-transport.ts new file mode 100644 index 00000000000..8cf93a77844 --- /dev/null +++ b/apps/sim/lib/oauth/google-service-account-transport.ts @@ -0,0 +1,79 @@ +import { parseRetryAfter } from '@sim/utils/retry' +import { + isRetryableError, + readBoundedHttpErrorPayload, + retryWithExponentialBackoff, +} from '@/lib/knowledge/documents/utils' + +const RETRYABLE_STATUSES = new Set([429, 500, 502, 503, 504]) +const TOKEN_EXCHANGE_BUDGET_MS = 30_000 + +interface GoogleTokenExchangeResponse { + ok: boolean + status: number + body: string +} + +/** Keeps provider bodies and JWT assertions out of retry diagnostics. */ +class TokenExchangeRetryError extends Error { + readonly status: number + readonly retryAfterMs?: number + + constructor(response: Response) { + super(`Google service account token exchange failed (HTTP ${response.status})`) + this.status = response.status + this.retryAfterMs = + parseRetryAfter(response.headers.get('Retry-After'), Number.POSITIVE_INFINITY) ?? undefined + } +} + +/** Bounds token attempts and response consumption without changing final HTTP error classification. */ +export async function exchangeGoogleServiceAccountJwt( + tokenUri: string, + jwt: string, + signal?: AbortSignal +): Promise { + let lastResponse: GoogleTokenExchangeResponse | undefined + try { + return await retryWithExponentialBackoff( + async (attemptSignal) => { + lastResponse = undefined + const response = await fetch(tokenUri, { + method: 'POST', + redirect: 'error', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: new URLSearchParams({ + grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer', + assertion: jwt, + }), + signal: attemptSignal, + }) + const payload = await readBoundedHttpErrorPayload(response) + attemptSignal.throwIfAborted() + if (response.ok && !payload.ok) + throw new Error('Google service account token response could not be read') + lastResponse = { + ok: response.ok, + status: response.status, + body: payload.ok ? payload.body : '', + } + if (RETRYABLE_STATUSES.has(response.status)) { + throw new TokenExchangeRetryError(response) + } + return lastResponse + }, + { + maxRetries: 2, + initialDelayMs: 1000, + maxDelayMs: 2000, + retryBudgetMs: TOKEN_EXCHANGE_BUDGET_MS, + signal, + retryCondition: (error) => + error instanceof TokenExchangeRetryError || isRetryableError(error), + } + ) + } catch (error) { + if (error instanceof TokenExchangeRetryError && lastResponse) return lastResponse + throw error + } +} diff --git a/packages/db/migrations/0356_connector_partition_progress.sql b/packages/db/migrations/0356_connector_partition_progress.sql new file mode 100644 index 00000000000..1f0bbb2cdc9 --- /dev/null +++ b/packages/db/migrations/0356_connector_partition_progress.sql @@ -0,0 +1,28 @@ +CREATE TABLE "knowledge_connector_partition" ( + "connector_id" text NOT NULL, + "partition_key" text NOT NULL, + "generation_id" text NOT NULL, + "context" jsonb NOT NULL, + "cursor" text, + "status" text DEFAULT 'pending' NOT NULL, + "attempts" integer DEFAULT 0 NOT NULL, + "retry_at" timestamp DEFAULT now() NOT NULL, + "last_served_at" timestamp, + "failure" jsonb, + "permission_cursor" text, + "permission_attempts" integer DEFAULT 0 NOT NULL, + "permission_retry_at" timestamp NOT NULL, + "permission_last_served_at" timestamp, + "permission_started_at" timestamp, + "permission_failure" jsonb, + CONSTRAINT "kcp_pk" PRIMARY KEY("connector_id","partition_key"), + CONSTRAINT "kcp_partition_key_check" CHECK (octet_length("knowledge_connector_partition"."partition_key") BETWEEN 1 AND 1024), + CONSTRAINT "kcp_context_check" CHECK (jsonb_typeof("knowledge_connector_partition"."context") = 'object' AND octet_length("knowledge_connector_partition"."context"::text) <= 16384), + CONSTRAINT "kcp_status_check" CHECK ("knowledge_connector_partition"."status" IN ('pending', 'complete', 'blocked')), + CONSTRAINT "kcp_cursor_check" CHECK (("knowledge_connector_partition"."cursor" IS NULL OR octet_length("knowledge_connector_partition"."cursor") <= 393216) AND ("knowledge_connector_partition"."permission_cursor" IS NULL OR octet_length("knowledge_connector_partition"."permission_cursor") <= 393216)), + CONSTRAINT "kcp_attempts_check" CHECK ("knowledge_connector_partition"."attempts" >= 0 AND "knowledge_connector_partition"."permission_attempts" >= 0) +); +--> statement-breakpoint +ALTER TABLE "knowledge_connector_partition" ADD CONSTRAINT "knowledge_connector_partition_connector_id_knowledge_connector_id_fk" FOREIGN KEY ("connector_id") REFERENCES "public"."knowledge_connector"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "kcp_content_due_idx" ON "knowledge_connector_partition" USING btree ("connector_id","generation_id","status","retry_at","last_served_at");--> statement-breakpoint +CREATE INDEX "kcp_permission_due_idx" ON "knowledge_connector_partition" USING btree ("connector_id","generation_id","permission_retry_at","permission_last_served_at"); \ No newline at end of file diff --git a/packages/db/migrations/meta/0356_snapshot.json b/packages/db/migrations/meta/0356_snapshot.json new file mode 100644 index 00000000000..2ef177f6e52 --- /dev/null +++ b/packages/db/migrations/meta/0356_snapshot.json @@ -0,0 +1,27509 @@ +{ + "id": "91de1d0e-ad87-4ad5-bef8-2c77aa34f2f7", + "prevId": "ae380daf-a120-4f9d-8908-caa1c0980f76", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.academy_certificate": { + "name": "academy_certificate", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "course_id": { + "name": "course_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "academy_cert_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "issued_at": { + "name": "issued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "certificate_number": { + "name": "certificate_number", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "academy_certificate_user_id_idx": { + "name": "academy_certificate_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "academy_certificate_course_id_idx": { + "name": "academy_certificate_course_id_idx", + "columns": [ + { + "expression": "course_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "academy_certificate_user_course_unique": { + "name": "academy_certificate_user_course_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "course_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "academy_certificate_status_idx": { + "name": "academy_certificate_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "academy_certificate_user_id_user_id_fk": { + "name": "academy_certificate_user_id_user_id_fk", + "tableFrom": "academy_certificate", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "academy_certificate_certificate_number_unique": { + "name": "academy_certificate_certificate_number_unique", + "nullsNotDistinct": false, + "columns": ["certificate_number"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oauth_config": { + "name": "oauth_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "account_user_id_idx": { + "name": "account_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_account_on_account_id_provider_id": { + "name": "idx_account_on_account_id_provider_id", + "columns": [ + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "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.api_key": { + "name": "api_key", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'personal'" + }, + "last_used": { + "name": "last_used", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "api_key_workspace_type_idx": { + "name": "api_key_workspace_type_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "api_key_user_type_idx": { + "name": "api_key_user_type_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "api_key_key_hash_idx": { + "name": "api_key_key_hash_idx", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "api_key_user_id_user_id_fk": { + "name": "api_key_user_id_user_id_fk", + "tableFrom": "api_key", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "api_key_workspace_id_workspace_id_fk": { + "name": "api_key_workspace_id_workspace_id_fk", + "tableFrom": "api_key", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "api_key_created_by_user_id_fk": { + "name": "api_key_created_by_user_id_fk", + "tableFrom": "api_key", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "api_key_key_unique": { + "name": "api_key_key_unique", + "nullsNotDistinct": false, + "columns": ["key"] + } + }, + "policies": {}, + "checkConstraints": { + "workspace_type_check": { + "name": "workspace_type_check", + "value": "(type = 'workspace' AND workspace_id IS NOT NULL) OR (type = 'personal' AND workspace_id IS NULL)" + } + }, + "isRLSEnabled": false + }, + "public.async_jobs": { + "name": "async_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "run_at": { + "name": "run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "max_attempts": { + "name": "max_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 3 + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "output": { + "name": "output", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "async_jobs_status_started_at_idx": { + "name": "async_jobs_status_started_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "async_jobs_status_completed_at_idx": { + "name": "async_jobs_status_completed_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "completed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "async_jobs_schedule_pending_run_at_idx": { + "name": "async_jobs_schedule_pending_run_at_idx", + "columns": [ + { + "expression": "run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"async_jobs\".\"type\" = 'schedule-execution' AND \"async_jobs\".\"status\" = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "async_jobs_schedule_processing_started_at_idx": { + "name": "async_jobs_schedule_processing_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"async_jobs\".\"type\" = 'schedule-execution' AND \"async_jobs\".\"status\" = 'processing'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "async_jobs_schedule_unreconciled_terminal_idx": { + "name": "async_jobs_schedule_unreconciled_terminal_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"async_jobs\".\"type\" = 'schedule-execution' AND \"async_jobs\".\"status\" IN ('completed', 'failed', 'cancelled') AND COALESCE(\"async_jobs\".\"metadata\" ->> 'scheduleReconciled', 'false') <> 'true'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.audit_log": { + "name": "audit_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_name": { + "name": "actor_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_email": { + "name": "actor_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resource_name": { + "name": "resource_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "surface": { + "name": "surface", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "audit_log_workspace_created_idx": { + "name": "audit_log_workspace_created_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_log_workspace_created_at_id_idx": { + "name": "audit_log_workspace_created_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"created_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_log_actor_created_idx": { + "name": "audit_log_actor_created_idx", + "columns": [ + { + "expression": "actor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_log_resource_idx": { + "name": "audit_log_resource_idx", + "columns": [ + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_log_action_idx": { + "name": "audit_log_action_idx", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "audit_log_workspace_id_workspace_id_fk": { + "name": "audit_log_workspace_id_workspace_id_fk", + "tableFrom": "audit_log", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "audit_log_actor_id_user_id_fk": { + "name": "audit_log_actor_id_user_id_fk", + "tableFrom": "audit_log", + "tableTo": "user", + "columnsFrom": ["actor_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.background_work_status": { + "name": "background_work_status", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "background_work_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "background_work_status_value", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "background_work_status_workspace_status_idx": { + "name": "background_work_status_workspace_status_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "background_work_status_workflow_status_idx": { + "name": "background_work_status_workflow_status_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "background_work_status_meta_child_ws_idx": { + "name": "background_work_status_meta_child_ws_idx", + "columns": [ + { + "expression": "(\"metadata\" ->> 'childWorkspaceId')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "background_work_status_meta_other_ws_idx": { + "name": "background_work_status_meta_other_ws_idx", + "columns": [ + { + "expression": "(\"metadata\" ->> 'otherWorkspaceId')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "background_work_status_workspace_id_workspace_id_fk": { + "name": "background_work_status_workspace_id_workspace_id_fk", + "tableFrom": "background_work_status", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "background_work_status_workflow_id_workflow_id_fk": { + "name": "background_work_status_workflow_id_workflow_id_fk", + "tableFrom": "background_work_status", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat": { + "name": "chat", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "customizations": { + "name": "customizations", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'public'" + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "allowed_emails": { + "name": "allowed_emails", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "output_configs": { + "name": "output_configs", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "include_thinking": { + "name": "include_thinking", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "include_tool_calls": { + "name": "include_tool_calls", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "identifier_idx": { + "name": "identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_archived_at_partial_idx": { + "name": "chat_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"chat\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_chat_on_workflow_id_archived_at": { + "name": "idx_chat_on_workflow_id_archived_at", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_workflow_id_workflow_id_fk": { + "name": "chat_workflow_id_workflow_id_fk", + "tableFrom": "chat", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_user_id_user_id_fk": { + "name": "chat_user_id_user_id_fk", + "tableFrom": "chat", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_async_tool_calls": { + "name": "copilot_async_tool_calls", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "checkpoint_id": { + "name": "checkpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "tool_call_id": { + "name": "tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "args": { + "name": "args", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "status": { + "name": "status", + "type": "copilot_async_tool_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "permission_decision": { + "name": "permission_decision", + "type": "copilot_tool_permission_decision", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "permission_decided_at": { + "name": "permission_decided_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "claimed_by": { + "name": "claimed_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_async_tool_calls_run_id_idx": { + "name": "copilot_async_tool_calls_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_checkpoint_id_idx": { + "name": "copilot_async_tool_calls_checkpoint_id_idx", + "columns": [ + { + "expression": "checkpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_status_idx": { + "name": "copilot_async_tool_calls_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_run_status_idx": { + "name": "copilot_async_tool_calls_run_status_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_tool_call_id_unique": { + "name": "copilot_async_tool_calls_tool_call_id_unique", + "columns": [ + { + "expression": "tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_async_tool_calls_run_id_copilot_runs_id_fk": { + "name": "copilot_async_tool_calls_run_id_copilot_runs_id_fk", + "tableFrom": "copilot_async_tool_calls", + "tableTo": "copilot_runs", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_async_tool_calls_checkpoint_id_copilot_run_checkpoints_id_fk": { + "name": "copilot_async_tool_calls_checkpoint_id_copilot_run_checkpoints_id_fk", + "tableFrom": "copilot_async_tool_calls", + "tableTo": "copilot_run_checkpoints", + "columnsFrom": ["checkpoint_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_chats": { + "name": "copilot_chats", + "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 + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "chat_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'copilot'" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'claude-3-7-sonnet-latest'" + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_conversation_key": { + "name": "external_conversation_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_conversation_metadata": { + "name": "external_conversation_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "preview_yaml": { + "name": "preview_yaml", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "plan_artifact": { + "name": "plan_artifact", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "resources": { + "name": "resources", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "auto_allowed_tools": { + "name": "auto_allowed_tools", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "pinned": { + "name": "pinned", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_chats_organization_id_idx": { + "name": "copilot_chats_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_external_conversation_unique": { + "name": "copilot_chats_external_conversation_unique", + "columns": [ + { + "expression": "external_conversation_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"copilot_chats\".\"external_conversation_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_user_org_created_idx": { + "name": "copilot_chats_user_org_created_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_user_id_idx": { + "name": "copilot_chats_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_workflow_id_idx": { + "name": "copilot_chats_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_user_workflow_idx": { + "name": "copilot_chats_user_workflow_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_user_workspace_idx": { + "name": "copilot_chats_user_workspace_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_created_at_idx": { + "name": "copilot_chats_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_updated_at_idx": { + "name": "copilot_chats_updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_workspace_created_at_id_idx": { + "name": "copilot_chats_workspace_created_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"created_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_user_workspace_deleted_partial_idx": { + "name": "copilot_chats_user_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"copilot_chats\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_chats_user_id_user_id_fk": { + "name": "copilot_chats_user_id_user_id_fk", + "tableFrom": "copilot_chats", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_chats_workflow_id_workflow_id_fk": { + "name": "copilot_chats_workflow_id_workflow_id_fk", + "tableFrom": "copilot_chats", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_chats_workspace_id_workspace_id_fk": { + "name": "copilot_chats_workspace_id_workspace_id_fk", + "tableFrom": "copilot_chats", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_chats_organization_id_organization_id_fk": { + "name": "copilot_chats_organization_id_organization_id_fk", + "tableFrom": "copilot_chats", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "copilot_chats_owner_check": { + "name": "copilot_chats_owner_check", + "value": "num_nonnulls(\"copilot_chats\".\"workspace_id\", \"copilot_chats\".\"organization_id\") <= 1" + }, + "copilot_chats_organization_workflow_check": { + "name": "copilot_chats_organization_workflow_check", + "value": "\"copilot_chats\".\"organization_id\" IS NULL OR \"copilot_chats\".\"workflow_id\" IS NULL" + } + }, + "isRLSEnabled": false + }, + "public.copilot_feedback": { + "name": "copilot_feedback", + "schema": "", + "columns": { + "feedback_id": { + "name": "feedback_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_query": { + "name": "user_query", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_response": { + "name": "agent_response", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_positive": { + "name": "is_positive", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "feedback": { + "name": "feedback", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_yaml": { + "name": "workflow_yaml", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_feedback_user_id_idx": { + "name": "copilot_feedback_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_feedback_chat_id_idx": { + "name": "copilot_feedback_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_feedback_user_chat_idx": { + "name": "copilot_feedback_user_chat_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_feedback_is_positive_idx": { + "name": "copilot_feedback_is_positive_idx", + "columns": [ + { + "expression": "is_positive", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_feedback_created_at_idx": { + "name": "copilot_feedback_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_feedback_user_id_user_id_fk": { + "name": "copilot_feedback_user_id_user_id_fk", + "tableFrom": "copilot_feedback", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_feedback_chat_id_copilot_chats_id_fk": { + "name": "copilot_feedback_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_feedback", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_messages": { + "name": "copilot_messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "stream_id": { + "name": "stream_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "parent_message_id": { + "name": "parent_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tokens_in": { + "name": "tokens_in", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "tokens_out": { + "name": "tokens_out", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "seq": { + "name": "seq", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_messages_chat_message_unique": { + "name": "copilot_messages_chat_message_unique", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_messages_chat_created_at_idx": { + "name": "copilot_messages_chat_created_at_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"copilot_messages\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_messages_chat_seq_idx": { + "name": "copilot_messages_chat_seq_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "seq", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"copilot_messages\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_messages_chat_stream_idx": { + "name": "copilot_messages_chat_stream_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stream_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"copilot_messages\".\"stream_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_messages_user_created_at_idx": { + "name": "copilot_messages_user_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"copilot_messages\".\"role\" = 'user' AND \"copilot_messages\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_messages_chat_id_copilot_chats_id_fk": { + "name": "copilot_messages_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_messages", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_run_checkpoints": { + "name": "copilot_run_checkpoints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pending_tool_call_id": { + "name": "pending_tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "conversation_snapshot": { + "name": "conversation_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "agent_state": { + "name": "agent_state", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "provider_request": { + "name": "provider_request", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_run_checkpoints_run_id_idx": { + "name": "copilot_run_checkpoints_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_run_checkpoints_pending_tool_call_id_idx": { + "name": "copilot_run_checkpoints_pending_tool_call_id_idx", + "columns": [ + { + "expression": "pending_tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_run_checkpoints_run_pending_tool_unique": { + "name": "copilot_run_checkpoints_run_pending_tool_unique", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pending_tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_run_checkpoints_run_id_copilot_runs_id_fk": { + "name": "copilot_run_checkpoints_run_id_copilot_runs_id_fk", + "tableFrom": "copilot_run_checkpoints", + "tableTo": "copilot_runs", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_runs": { + "name": "copilot_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_run_id": { + "name": "parent_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stream_id": { + "name": "stream_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent": { + "name": "agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "copilot_run_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "request_context": { + "name": "request_context", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "copilot_runs_execution_id_idx": { + "name": "copilot_runs_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_parent_run_id_idx": { + "name": "copilot_runs_parent_run_id_idx", + "columns": [ + { + "expression": "parent_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_chat_id_idx": { + "name": "copilot_runs_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_chat_started_at_idx": { + "name": "copilot_runs_chat_started_at_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_user_id_idx": { + "name": "copilot_runs_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_workflow_id_idx": { + "name": "copilot_runs_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_workspace_id_idx": { + "name": "copilot_runs_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_status_idx": { + "name": "copilot_runs_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_chat_execution_idx": { + "name": "copilot_runs_chat_execution_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_execution_started_at_idx": { + "name": "copilot_runs_execution_started_at_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_workspace_completed_at_id_idx": { + "name": "copilot_runs_workspace_completed_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"completed_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_stream_id_unique": { + "name": "copilot_runs_stream_id_unique", + "columns": [ + { + "expression": "stream_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_runs_chat_id_copilot_chats_id_fk": { + "name": "copilot_runs_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_runs", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_runs_user_id_user_id_fk": { + "name": "copilot_runs_user_id_user_id_fk", + "tableFrom": "copilot_runs", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_runs_workflow_id_workflow_id_fk": { + "name": "copilot_runs_workflow_id_workflow_id_fk", + "tableFrom": "copilot_runs", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_runs_workspace_id_workspace_id_fk": { + "name": "copilot_runs_workspace_id_workspace_id_fk", + "tableFrom": "copilot_runs", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_workflow_read_hashes": { + "name": "copilot_workflow_read_hashes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_workflow_read_hashes_chat_id_idx": { + "name": "copilot_workflow_read_hashes_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_workflow_read_hashes_workflow_id_idx": { + "name": "copilot_workflow_read_hashes_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_workflow_read_hashes_chat_workflow_unique": { + "name": "copilot_workflow_read_hashes_chat_workflow_unique", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_workflow_read_hashes_chat_id_copilot_chats_id_fk": { + "name": "copilot_workflow_read_hashes_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_workflow_read_hashes", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_workflow_read_hashes_workflow_id_workflow_id_fk": { + "name": "copilot_workflow_read_hashes_workflow_id_workflow_id_fk", + "tableFrom": "copilot_workflow_read_hashes", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.credential": { + "name": "credential", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slack_app_id": { + "name": "slack_app_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "credential_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "unredacted": { + "name": "unredacted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env_key": { + "name": "env_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env_owner_user_id": { + "name": "env_owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_service_account_key": { + "name": "encrypted_service_account_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_personal_token": { + "name": "encrypted_personal_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "authorization_app_id": { + "name": "authorization_app_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_group_enrollment_id": { + "name": "credential_group_enrollment_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_group_option_id": { + "name": "credential_group_option_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mcp_server_id": { + "name": "mcp_server_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mcp_oauth_config_version": { + "name": "mcp_oauth_config_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "managed_oauth_scope_version": { + "name": "managed_oauth_scope_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "provider_subject_id": { + "name": "provider_subject_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_tenant_id": { + "name": "provider_tenant_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "managed_oauth_status": { + "name": "managed_oauth_status", + "type": "managed_oauth_credential_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "granted_scopes": { + "name": "granted_scopes", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "provider_metadata": { + "name": "provider_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "encrypted_oauth_token_set": { + "name": "encrypted_oauth_token_set", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mcp_tools": { + "name": "mcp_tools", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "mcp_tools_refreshed_at": { + "name": "mcp_tools_refreshed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "granted_at": { + "name": "granted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_refreshed_at": { + "name": "last_refreshed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "credential_organization_id_idx": { + "name": "credential_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_organization_account_unique": { + "name": "credential_organization_account_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"credential\".\"account_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_org_personal_token_unique": { + "name": "credential_org_personal_token_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_subject_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"credential\".\"type\" = 'personal_token'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_workspace_id_idx": { + "name": "credential_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_type_idx": { + "name": "credential_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_provider_id_idx": { + "name": "credential_provider_id_idx", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_account_id_idx": { + "name": "credential_account_id_idx", + "columns": [ + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_env_owner_user_id_idx": { + "name": "credential_env_owner_user_id_idx", + "columns": [ + { + "expression": "env_owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_group_enrollment_idx": { + "name": "credential_group_enrollment_idx", + "columns": [ + { + "expression": "credential_group_enrollment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_mcp_server_idx": { + "name": "credential_mcp_server_idx", + "columns": [ + { + "expression": "mcp_server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_group_option_unique": { + "name": "credential_group_option_unique", + "columns": [ + { + "expression": "credential_group_enrollment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "credential_group_option_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"credential\".\"type\" = 'managed_oauth'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_managed_mcp_enrollment_server_unique": { + "name": "credential_managed_mcp_enrollment_server_unique", + "columns": [ + { + "expression": "credential_group_enrollment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "mcp_server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"credential\".\"type\" = 'managed_mcp'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_workspace_account_unique": { + "name": "credential_workspace_account_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "account_id IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_workspace_env_unique": { + "name": "credential_workspace_env_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "type = 'env_workspace'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_workspace_personal_env_unique": { + "name": "credential_workspace_personal_env_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env_owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "type = 'env_personal'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_personal_token_identity_unique": { + "name": "credential_personal_token_identity_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_subject_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "type = 'personal_token'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "credential_workspace_id_workspace_id_fk": { + "name": "credential_workspace_id_workspace_id_fk", + "tableFrom": "credential", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_organization_id_organization_id_fk": { + "name": "credential_organization_id_organization_id_fk", + "tableFrom": "credential", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_slack_app_id_slack_app_id_fk": { + "name": "credential_slack_app_id_slack_app_id_fk", + "tableFrom": "credential", + "tableTo": "slack_app", + "columnsFrom": ["slack_app_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "credential_account_id_account_id_fk": { + "name": "credential_account_id_account_id_fk", + "tableFrom": "credential", + "tableTo": "account", + "columnsFrom": ["account_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_env_owner_user_id_user_id_fk": { + "name": "credential_env_owner_user_id_user_id_fk", + "tableFrom": "credential", + "tableTo": "user", + "columnsFrom": ["env_owner_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_credential_group_enrollment_id_credential_group_enrollment_id_fk": { + "name": "credential_credential_group_enrollment_id_credential_group_enrollment_id_fk", + "tableFrom": "credential", + "tableTo": "credential_group_enrollment", + "columnsFrom": ["credential_group_enrollment_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_mcp_server_id_mcp_servers_id_fk": { + "name": "credential_mcp_server_id_mcp_servers_id_fk", + "tableFrom": "credential", + "tableTo": "mcp_servers", + "columnsFrom": ["mcp_server_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_created_by_user_id_fk": { + "name": "credential_created_by_user_id_fk", + "tableFrom": "credential", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "credential_owner_check": { + "name": "credential_owner_check", + "value": "num_nonnulls(\"credential\".\"workspace_id\", \"credential\".\"organization_id\") = 1" + }, + "credential_organization_type_check": { + "name": "credential_organization_type_check", + "value": "\"credential\".\"organization_id\" IS NULL OR \"credential\".\"type\" IN ('oauth', 'managed_oauth', 'managed_mcp', 'service_account', 'personal_token')" + }, + "credential_personal_token_source_check": { + "name": "credential_personal_token_source_check", + "value": "(type::text <> 'personal_token') OR (\n created_by IS NOT NULL\n AND provider_id IS NOT NULL\n AND provider_id = 'gitlab'\n AND provider_subject_id IS NOT NULL\n AND provider_tenant_id IS NOT NULL\n AND encrypted_personal_token IS NOT NULL\n AND granted_scopes IS NOT NULL\n AND cardinality(granted_scopes) > 0\n AND account_id IS NULL\n AND env_key IS NULL\n AND env_owner_user_id IS NULL\n AND authorization_app_id IS NULL\n AND encrypted_oauth_token_set IS NULL\n AND encrypted_service_account_key IS NULL\n AND unredacted = false\n )" + }, + "credential_oauth_source_check": { + "name": "credential_oauth_source_check", + "value": "(type <> 'oauth') OR (account_id IS NOT NULL AND provider_id IS NOT NULL)" + }, + "credential_managed_oauth_source_check": { + "name": "credential_managed_oauth_source_check", + "value": "(type::text <> 'managed_oauth') OR (\n account_id IS NULL\n AND provider_id IS NOT NULL\n AND authorization_app_id IS NOT NULL\n AND provider_subject_id IS NOT NULL\n AND managed_oauth_status IS NOT NULL\n AND granted_scopes IS NOT NULL\n AND encrypted_oauth_token_set IS NOT NULL\n AND granted_at IS NOT NULL\n )" + }, + "credential_managed_oauth_group_binding_check": { + "name": "credential_managed_oauth_group_binding_check", + "value": "(type::text <> 'managed_oauth') OR (\n credential_group_enrollment_id IS NOT NULL\n AND credential_group_option_id IS NOT NULL\n AND managed_oauth_scope_version IS NOT NULL\n AND managed_oauth_scope_version > 0\n )" + }, + "credential_managed_mcp_source_check": { + "name": "credential_managed_mcp_source_check", + "value": "(type::text <> 'managed_mcp') OR (\n id LIKE 'mcp-cg-%'\n AND account_id IS NULL\n AND provider_id IS NULL\n AND authorization_app_id IS NULL\n AND credential_group_enrollment_id IS NOT NULL\n AND credential_group_option_id IS NULL\n AND mcp_server_id IS NOT NULL\n AND managed_oauth_status IS NOT NULL\n AND (managed_oauth_status <> 'active' OR (\n encrypted_oauth_token_set IS NOT NULL\n AND mcp_tools IS NOT NULL\n ))\n AND granted_at IS NOT NULL\n AND managed_oauth_scope_version IS NULL\n AND provider_subject_id IS NULL\n AND provider_tenant_id IS NULL\n AND granted_scopes IS NULL\n AND provider_metadata IS NULL\n AND created_by IS NULL\n AND env_key IS NULL\n AND env_owner_user_id IS NULL\n AND encrypted_service_account_key IS NULL\n AND unredacted = false\n )" + }, + "credential_creator_source_check": { + "name": "credential_creator_source_check", + "value": "(type::text = 'managed_mcp') OR created_by IS NOT NULL" + }, + "credential_workspace_env_source_check": { + "name": "credential_workspace_env_source_check", + "value": "(type <> 'env_workspace') OR (env_key IS NOT NULL AND env_owner_user_id IS NULL)" + }, + "credential_personal_env_source_check": { + "name": "credential_personal_env_source_check", + "value": "(type <> 'env_personal') OR (env_key IS NOT NULL AND env_owner_user_id IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.credential_group": { + "name": "credential_group", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "public_id": { + "name": "public_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "options": { + "name": "options", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "encrypted_provider_configuration": { + "name": "encrypted_provider_configuration", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "credential_group_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "credential_group_organization_id_idx": { + "name": "credential_group_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_group_organization_unique": { + "name": "credential_group_organization_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_group_public_id_unique": { + "name": "credential_group_public_id_unique", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_group_workspace_unique": { + "name": "credential_group_workspace_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "credential_group_workspace_id_workspace_id_fk": { + "name": "credential_group_workspace_id_workspace_id_fk", + "tableFrom": "credential_group", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_group_organization_id_organization_id_fk": { + "name": "credential_group_organization_id_organization_id_fk", + "tableFrom": "credential_group", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_group_created_by_user_id_fk": { + "name": "credential_group_created_by_user_id_fk", + "tableFrom": "credential_group", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "credential_group_owner_check": { + "name": "credential_group_owner_check", + "value": "num_nonnulls(\"credential_group\".\"workspace_id\", \"credential_group\".\"organization_id\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.credential_group_enrollment": { + "name": "credential_group_enrollment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "credential_group_id": { + "name": "credential_group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "credential_group_enrollment_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'invited'" + }, + "invitation_token_hash": { + "name": "invitation_token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "invitation_expires_at": { + "name": "invitation_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "invited_at": { + "name": "invited_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "sent_at": { + "name": "sent_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_delivery_error": { + "name": "last_delivery_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "credential_group_enrollment_group_user_unique": { + "name": "credential_group_enrollment_group_user_unique", + "columns": [ + { + "expression": "credential_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"credential_group_enrollment\".\"user_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_group_enrollment_user_id_idx": { + "name": "credential_group_enrollment_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_group_enrollment_group_email_unique": { + "name": "credential_group_enrollment_group_email_unique", + "columns": [ + { + "expression": "credential_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_group_enrollment_invitation_token_hash_unique": { + "name": "credential_group_enrollment_invitation_token_hash_unique", + "columns": [ + { + "expression": "invitation_token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_group_enrollment_group_status_idx": { + "name": "credential_group_enrollment_group_status_idx", + "columns": [ + { + "expression": "credential_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_group_enrollment_group_invited_at_id_idx": { + "name": "credential_group_enrollment_group_invited_at_id_idx", + "columns": [ + { + "expression": "credential_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "invited_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "credential_group_enrollment_credential_group_id_credential_group_id_fk": { + "name": "credential_group_enrollment_credential_group_id_credential_group_id_fk", + "tableFrom": "credential_group_enrollment", + "tableTo": "credential_group", + "columnsFrom": ["credential_group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_group_enrollment_user_id_user_id_fk": { + "name": "credential_group_enrollment_user_id_user_id_fk", + "tableFrom": "credential_group_enrollment", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_group_enrollment_created_by_user_id_fk": { + "name": "credential_group_enrollment_created_by_user_id_fk", + "tableFrom": "credential_group_enrollment", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "credential_group_enrollment_normalized_email_check": { + "name": "credential_group_enrollment_normalized_email_check", + "value": "\"credential_group_enrollment\".\"email\" = lower(btrim(\"credential_group_enrollment\".\"email\")) AND length(\"credential_group_enrollment\".\"email\") BETWEEN 3 AND 320" + }, + "credential_group_enrollment_invitation_token_hash_length_check": { + "name": "credential_group_enrollment_invitation_token_hash_length_check", + "value": "length(\"credential_group_enrollment\".\"invitation_token_hash\") = 64" + } + }, + "isRLSEnabled": false + }, + "public.credential_member": { + "name": "credential_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "credential_member_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "status": { + "name": "status", + "type": "credential_member_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "invited_by": { + "name": "invited_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "credential_member_user_id_idx": { + "name": "credential_member_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_member_role_idx": { + "name": "credential_member_role_idx", + "columns": [ + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_member_status_idx": { + "name": "credential_member_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_member_unique": { + "name": "credential_member_unique", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "credential_member_credential_id_credential_id_fk": { + "name": "credential_member_credential_id_credential_id_fk", + "tableFrom": "credential_member", + "tableTo": "credential", + "columnsFrom": ["credential_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_member_user_id_user_id_fk": { + "name": "credential_member_user_id_user_id_fk", + "tableFrom": "credential_member", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_member_invited_by_user_id_fk": { + "name": "credential_member_invited_by_user_id_fk", + "tableFrom": "credential_member", + "tableTo": "user", + "columnsFrom": ["invited_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.custom_block": { + "name": "custom_block", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "icon_url": { + "name": "icon_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "inputs": { + "name": "inputs", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "outputs": { + "name": "outputs", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "trace_child_runs": { + "name": "trace_child_runs", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "custom_block_organization_id_idx": { + "name": "custom_block_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "custom_block_workflow_id_idx": { + "name": "custom_block_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "custom_block_organization_type_unique": { + "name": "custom_block_organization_type_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "custom_block_organization_id_organization_id_fk": { + "name": "custom_block_organization_id_organization_id_fk", + "tableFrom": "custom_block", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "custom_block_workflow_id_workflow_id_fk": { + "name": "custom_block_workflow_id_workflow_id_fk", + "tableFrom": "custom_block", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "custom_block_created_by_user_id_fk": { + "name": "custom_block_created_by_user_id_fk", + "tableFrom": "custom_block", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.custom_tools": { + "name": "custom_tools", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schema": { + "name": "schema", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "custom_tools_workspace_id_idx": { + "name": "custom_tools_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "custom_tools_workspace_title_unique": { + "name": "custom_tools_workspace_title_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "custom_tools_workspace_id_workspace_id_fk": { + "name": "custom_tools_workspace_id_workspace_id_fk", + "tableFrom": "custom_tools", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "custom_tools_user_id_user_id_fk": { + "name": "custom_tools_user_id_user_id_fk", + "tableFrom": "custom_tools", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.data_drain_runs": { + "name": "data_drain_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "drain_id": { + "name": "drain_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "data_drain_run_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "trigger": { + "name": "trigger", + "type": "data_drain_run_trigger", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "rows_exported": { + "name": "rows_exported", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "bytes_written": { + "name": "bytes_written", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cursor_before": { + "name": "cursor_before", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cursor_after": { + "name": "cursor_after", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "locators": { + "name": "locators", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": { + "data_drain_runs_drain_started_idx": { + "name": "data_drain_runs_drain_started_idx", + "columns": [ + { + "expression": "drain_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "data_drain_runs_drain_id_data_drains_id_fk": { + "name": "data_drain_runs_drain_id_data_drains_id_fk", + "tableFrom": "data_drain_runs", + "tableTo": "data_drains", + "columnsFrom": ["drain_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.data_drains": { + "name": "data_drains", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "data_drain_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "destination_type": { + "name": "destination_type", + "type": "data_drain_destination", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "destination_config": { + "name": "destination_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "destination_credentials": { + "name": "destination_credentials", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schedule_cadence": { + "name": "schedule_cadence", + "type": "data_drain_cadence", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "cursor": { + "name": "cursor", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_success_at": { + "name": "last_success_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "data_drains_org_idx": { + "name": "data_drains_org_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "data_drains_due_idx": { + "name": "data_drains_due_idx", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "data_drains_org_name_unique": { + "name": "data_drains_org_name_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "data_drains_organization_id_organization_id_fk": { + "name": "data_drains_organization_id_organization_id_fk", + "tableFrom": "data_drains", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "data_drains_created_by_user_id_fk": { + "name": "data_drains_created_by_user_id_fk", + "tableFrom": "data_drains", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.docs_embeddings": { + "name": "docs_embeddings", + "schema": "", + "columns": { + "chunk_id": { + "name": "chunk_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "chunk_text": { + "name": "chunk_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_document": { + "name": "source_document", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_link": { + "name": "source_link", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "header_text": { + "name": "header_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "header_level": { + "name": "header_level", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "embedding": { + "name": "embedding", + "type": "vector(1536)", + "primaryKey": false, + "notNull": true + }, + "embedding_model": { + "name": "embedding_model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text-embedding-3-small'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "chunk_text_tsv": { + "name": "chunk_text_tsv", + "type": "tsvector", + "primaryKey": false, + "notNull": false, + "generated": { + "as": "to_tsvector('english', \"docs_embeddings\".\"chunk_text\")", + "type": "stored" + } + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "docs_emb_source_document_idx": { + "name": "docs_emb_source_document_idx", + "columns": [ + { + "expression": "source_document", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_header_level_idx": { + "name": "docs_emb_header_level_idx", + "columns": [ + { + "expression": "header_level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_source_header_idx": { + "name": "docs_emb_source_header_idx", + "columns": [ + { + "expression": "source_document", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "header_level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_model_idx": { + "name": "docs_emb_model_idx", + "columns": [ + { + "expression": "embedding_model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_created_at_idx": { + "name": "docs_emb_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_embedding_vector_hnsw_idx": { + "name": "docs_embedding_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "docs_emb_metadata_gin_idx": { + "name": "docs_emb_metadata_gin_idx", + "columns": [ + { + "expression": "metadata", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "docs_emb_chunk_text_fts_idx": { + "name": "docs_emb_chunk_text_fts_idx", + "columns": [ + { + "expression": "chunk_text_tsv", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "docs_embedding_not_null_check": { + "name": "docs_embedding_not_null_check", + "value": "\"embedding\" IS NOT NULL" + }, + "docs_header_level_check": { + "name": "docs_header_level_check", + "value": "\"header_level\" >= 1 AND \"header_level\" <= 6" + } + }, + "isRLSEnabled": false + }, + "public.document": { + "name": "document", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "filename": { + "name": "filename", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_url": { + "name": "file_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "storage_key": { + "name": "storage_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "file_size": { + "name": "file_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chunk_count": { + "name": "chunk_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "character_count": { + "name": "character_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "processing_status": { + "name": "processing_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "processing_attempts": { + "name": "processing_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "processing_queued_at": { + "name": "processing_queued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "processing_queue_token": { + "name": "processing_queue_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "processing_started_at": { + "name": "processing_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "processing_deferred_until": { + "name": "processing_deferred_until", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "processing_completed_at": { + "name": "processing_completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "processing_error": { + "name": "processing_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "processing_recovery_after": { + "name": "processing_recovery_after", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "user_excluded": { + "name": "user_excluded", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "tag1": { + "name": "tag1", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag2": { + "name": "tag2", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag3": { + "name": "tag3", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag4": { + "name": "tag4", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag5": { + "name": "tag5", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag6": { + "name": "tag6", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag7": { + "name": "tag7", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "number1": { + "name": "number1", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number2": { + "name": "number2", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number3": { + "name": "number3", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number4": { + "name": "number4", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number5": { + "name": "number5", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "date1": { + "name": "date1", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "date2": { + "name": "date2", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "boolean1": { + "name": "boolean1", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean2": { + "name": "boolean2", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean3": { + "name": "boolean3", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_url": { + "name": "source_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_provenance_version": { + "name": "secret_provenance_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "uploaded_by": { + "name": "uploaded_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "acl": { + "name": "acl", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{ws}'::text[]" + }, + "acl_requirements": { + "name": "acl_requirements", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "acl_verified_at": { + "name": "acl_verified_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "source_modified_at": { + "name": "source_modified_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "source_seen_at": { + "name": "source_seen_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "doc_kb_id_idx": { + "name": "doc_kb_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_acl_gin_idx": { + "name": "doc_acl_gin_idx", + "columns": [ + { + "expression": "acl", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "array_ops" + } + ], + "isUnique": false, + "where": "\"document\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "gin", + "with": {} + }, + "doc_filename_idx": { + "name": "doc_filename_idx", + "columns": [ + { + "expression": "filename", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_processing_status_idx": { + "name": "doc_processing_status_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "processing_status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_processing_recovery_idx": { + "name": "doc_processing_recovery_idx", + "columns": [ + { + "expression": "uploaded_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"document\".\"processing_status\" IN ('pending', 'processing', 'failed') AND \"document\".\"connector_id\" IS NOT NULL AND \"document\".\"content_hash\" IS NOT NULL AND \"document\".\"storage_key\" IS NOT NULL AND \"document\".\"user_excluded\" = false AND \"document\".\"archived_at\" IS NULL AND \"document\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_connector_processing_status_idx": { + "name": "doc_connector_processing_status_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "processing_status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"document\".\"processing_status\" IN ('pending', 'processing', 'failed') AND \"document\".\"connector_id\" IS NOT NULL AND \"document\".\"user_excluded\" = false AND \"document\".\"archived_at\" IS NULL AND \"document\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_connector_external_id_idx": { + "name": "doc_connector_external_id_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"document\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_connector_source_lookup_idx": { + "name": "doc_connector_source_lookup_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_connector_reconciliation_idx": { + "name": "doc_connector_reconciliation_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "COALESCE(\"source_seen_at\", '-infinity'::timestamp)", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"document\".\"user_excluded\" = false AND \"document\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_active_kb_token_count_idx": { + "name": "doc_active_kb_token_count_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "token_count", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"document\".\"user_excluded\" = false AND \"document\".\"archived_at\" IS NULL AND \"document\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_storage_key_idx": { + "name": "doc_storage_key_idx", + "columns": [ + { + "expression": "storage_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"document\".\"storage_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_archived_at_partial_idx": { + "name": "doc_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"document\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_deleted_at_partial_idx": { + "name": "doc_deleted_at_partial_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"document\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_kb_tag1_lower_idx": { + "name": "doc_kb_tag1_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag1\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_kb_tag2_lower_idx": { + "name": "doc_kb_tag2_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag2\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_kb_tag3_lower_idx": { + "name": "doc_kb_tag3_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag3\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_kb_tag4_lower_idx": { + "name": "doc_kb_tag4_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag4\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_kb_tag5_lower_idx": { + "name": "doc_kb_tag5_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag5\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_kb_tag6_lower_idx": { + "name": "doc_kb_tag6_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag6\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_kb_tag7_lower_idx": { + "name": "doc_kb_tag7_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag7\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number1_idx": { + "name": "doc_number1_idx", + "columns": [ + { + "expression": "number1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number2_idx": { + "name": "doc_number2_idx", + "columns": [ + { + "expression": "number2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number3_idx": { + "name": "doc_number3_idx", + "columns": [ + { + "expression": "number3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number4_idx": { + "name": "doc_number4_idx", + "columns": [ + { + "expression": "number4", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number5_idx": { + "name": "doc_number5_idx", + "columns": [ + { + "expression": "number5", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_date1_idx": { + "name": "doc_date1_idx", + "columns": [ + { + "expression": "date1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_date2_idx": { + "name": "doc_date2_idx", + "columns": [ + { + "expression": "date2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean1_idx": { + "name": "doc_boolean1_idx", + "columns": [ + { + "expression": "boolean1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean2_idx": { + "name": "doc_boolean2_idx", + "columns": [ + { + "expression": "boolean2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean3_idx": { + "name": "doc_boolean3_idx", + "columns": [ + { + "expression": "boolean3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_knowledge_base_id_knowledge_base_id_fk": { + "name": "document_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "document", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_connector_id_knowledge_connector_id_fk": { + "name": "document_connector_id_knowledge_connector_id_fk", + "tableFrom": "document", + "tableTo": "knowledge_connector", + "columnsFrom": ["connector_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_uploaded_by_user_id_fk": { + "name": "document_uploaded_by_user_id_fk", + "tableFrom": "document", + "tableTo": "user", + "columnsFrom": ["uploaded_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "doc_acl_token_shape_check": { + "name": "doc_acl_token_shape_check", + "value": "array_position(\"document\".\"acl\", NULL) IS NULL AND (cardinality(\"document\".\"acl\") = 0 OR (cardinality(\"document\".\"acl\") = array_length(string_to_array(array_to_string(\"document\".\"acl\", E'\\n'), E'\\n'), 1) AND array_to_string(\"document\".\"acl\", E'\\n') ~ '^((ws|pub|link|u:[^\\nA-Z]+@[^\\nA-Z]+|[gs]:[^\\n:]+:[^\\n:]+:[^\\n]+)(\\n(ws|pub|link|u:[^\\nA-Z]+@[^\\nA-Z]+|[gs]:[^\\n:]+:[^\\n:]+:[^\\n]+))*)$'))" + } + }, + "isRLSEnabled": false + }, + "public.document_secret_provenance": { + "name": "document_secret_provenance", + "schema": "", + "columns": { + "document_id": { + "name": "document_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "source_hash": { + "name": "source_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entries": { + "name": "entries", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "document_secret_provenance_document_id_document_id_fk": { + "name": "document_secret_provenance_document_id_document_id_fk", + "tableFrom": "document_secret_provenance", + "tableTo": "document", + "columnsFrom": ["document_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "document_secret_provenance_status_check": { + "name": "document_secret_provenance_status_check", + "value": "\"document_secret_provenance\".\"status\" IN ('exact', 'unknown')" + } + }, + "isRLSEnabled": false + }, + "public.embedding": { + "name": "embedding", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chunk_index": { + "name": "chunk_index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "chunk_hash": { + "name": "chunk_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret_provenance_version": { + "name": "secret_provenance_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "content_length": { + "name": "content_length", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "embedding": { + "name": "embedding", + "type": "vector(1536)", + "primaryKey": false, + "notNull": false + }, + "embedding_384": { + "name": "embedding_384", + "type": "vector(384)", + "primaryKey": false, + "notNull": false + }, + "embedding_768": { + "name": "embedding_768", + "type": "vector(768)", + "primaryKey": false, + "notNull": false + }, + "embedding_1024": { + "name": "embedding_1024", + "type": "vector(1024)", + "primaryKey": false, + "notNull": false + }, + "embedding_3072": { + "name": "embedding_3072", + "type": "vector(3072)", + "primaryKey": false, + "notNull": false + }, + "embedding_model": { + "name": "embedding_model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text-embedding-3-small'" + }, + "start_offset": { + "name": "start_offset", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "end_offset": { + "name": "end_offset", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "tag1": { + "name": "tag1", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag2": { + "name": "tag2", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag3": { + "name": "tag3", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag4": { + "name": "tag4", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag5": { + "name": "tag5", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag6": { + "name": "tag6", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag7": { + "name": "tag7", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "number1": { + "name": "number1", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number2": { + "name": "number2", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number3": { + "name": "number3", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number4": { + "name": "number4", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number5": { + "name": "number5", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "date1": { + "name": "date1", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "date2": { + "name": "date2", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "boolean1": { + "name": "boolean1", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean2": { + "name": "boolean2", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean3": { + "name": "boolean3", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "content_tsv": { + "name": "content_tsv", + "type": "tsvector", + "primaryKey": false, + "notNull": false, + "generated": { + "as": "to_tsvector('english', \"embedding\".\"content\")", + "type": "stored" + } + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "emb_kb_id_idx": { + "name": "emb_kb_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_id_idx": { + "name": "emb_doc_id_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_chunk_idx": { + "name": "emb_doc_chunk_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chunk_index", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_model_idx": { + "name": "emb_kb_model_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "embedding_model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_enabled_idx": { + "name": "emb_kb_enabled_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_enabled_idx": { + "name": "emb_doc_enabled_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "embedding_vector_hnsw_idx": { + "name": "embedding_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_384_vector_hnsw_idx": { + "name": "embedding_384_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding_384", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_768_vector_hnsw_idx": { + "name": "embedding_768_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding_768", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_1024_vector_hnsw_idx": { + "name": "embedding_1024_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding_1024", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_3072_vector_hnsw_idx": { + "name": "embedding_3072_vector_hnsw_idx", + "columns": [ + { + "expression": "(\"embedding_3072\"::halfvec(3072)) halfvec_cosine_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_binary_hnsw_idx": { + "name": "embedding_binary_hnsw_idx", + "columns": [ + { + "expression": "(binary_quantize(\"embedding\")::bit(1536)) bit_hamming_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_384_binary_hnsw_idx": { + "name": "embedding_384_binary_hnsw_idx", + "columns": [ + { + "expression": "(binary_quantize(\"embedding_384\")::bit(384)) bit_hamming_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_768_binary_hnsw_idx": { + "name": "embedding_768_binary_hnsw_idx", + "columns": [ + { + "expression": "(binary_quantize(\"embedding_768\")::bit(768)) bit_hamming_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_1024_binary_hnsw_idx": { + "name": "embedding_1024_binary_hnsw_idx", + "columns": [ + { + "expression": "(binary_quantize(\"embedding_1024\")::bit(1024)) bit_hamming_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_3072_binary_hnsw_idx": { + "name": "embedding_3072_binary_hnsw_idx", + "columns": [ + { + "expression": "(binary_quantize(\"embedding_3072\")::bit(3072)) bit_hamming_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "emb_kb_tag1_lower_idx": { + "name": "emb_kb_tag1_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag1\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_tag2_lower_idx": { + "name": "emb_kb_tag2_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag2\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_tag3_lower_idx": { + "name": "emb_kb_tag3_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag3\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_tag4_lower_idx": { + "name": "emb_kb_tag4_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag4\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_tag5_lower_idx": { + "name": "emb_kb_tag5_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag5\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_tag6_lower_idx": { + "name": "emb_kb_tag6_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag6\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_tag7_lower_idx": { + "name": "emb_kb_tag7_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag7\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number1_idx": { + "name": "emb_number1_idx", + "columns": [ + { + "expression": "number1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number2_idx": { + "name": "emb_number2_idx", + "columns": [ + { + "expression": "number2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number3_idx": { + "name": "emb_number3_idx", + "columns": [ + { + "expression": "number3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number4_idx": { + "name": "emb_number4_idx", + "columns": [ + { + "expression": "number4", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number5_idx": { + "name": "emb_number5_idx", + "columns": [ + { + "expression": "number5", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_date1_idx": { + "name": "emb_date1_idx", + "columns": [ + { + "expression": "date1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_date2_idx": { + "name": "emb_date2_idx", + "columns": [ + { + "expression": "date2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean1_idx": { + "name": "emb_boolean1_idx", + "columns": [ + { + "expression": "boolean1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean2_idx": { + "name": "emb_boolean2_idx", + "columns": [ + { + "expression": "boolean2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean3_idx": { + "name": "emb_boolean3_idx", + "columns": [ + { + "expression": "boolean3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_content_fts_idx": { + "name": "emb_content_fts_idx", + "columns": [ + { + "expression": "content_tsv", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "embedding_knowledge_base_id_knowledge_base_id_fk": { + "name": "embedding_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "embedding", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "embedding_document_id_document_id_fk": { + "name": "embedding_document_id_document_id_fk", + "tableFrom": "embedding", + "tableTo": "document", + "columnsFrom": ["document_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "embedding_width_check": { + "name": "embedding_width_check", + "value": "num_nonnulls(\"embedding\", \"embedding_384\", \"embedding_768\", \"embedding_1024\", \"embedding_3072\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.embedding_keyword_search": { + "name": "embedding_keyword_search", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "content_tsv": { + "name": "content_tsv", + "type": "tsvector", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "embedding_keyword_search_kb_idx": { + "name": "embedding_keyword_search_kb_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "embedding_keyword_search_document_idx": { + "name": "embedding_keyword_search_document_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "embedding_keyword_search_content_idx": { + "name": "embedding_keyword_search_content_idx", + "columns": [ + { + "expression": "content_tsv", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "embedding_keyword_search_id_embedding_id_fk": { + "name": "embedding_keyword_search_id_embedding_id_fk", + "tableFrom": "embedding_keyword_search", + "tableTo": "embedding", + "columnsFrom": ["id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.embedding_search": { + "name": "embedding_search", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "binary": { + "name": "binary", + "type": "bit(1536)", + "primaryKey": false, + "notNull": false + }, + "binary_384": { + "name": "binary_384", + "type": "bit(384)", + "primaryKey": false, + "notNull": false + }, + "binary_768": { + "name": "binary_768", + "type": "bit(768)", + "primaryKey": false, + "notNull": false + }, + "binary_1024": { + "name": "binary_1024", + "type": "bit(1024)", + "primaryKey": false, + "notNull": false + }, + "binary_3072": { + "name": "binary_3072", + "type": "bit(3072)", + "primaryKey": false, + "notNull": false + }, + "vector": { + "name": "vector", + "type": "halfvec(1536)", + "primaryKey": false, + "notNull": false + }, + "vector_384": { + "name": "vector_384", + "type": "halfvec(384)", + "primaryKey": false, + "notNull": false + }, + "vector_512": { + "name": "vector_512", + "type": "halfvec(512)", + "primaryKey": false, + "notNull": false + }, + "vector_768": { + "name": "vector_768", + "type": "halfvec(768)", + "primaryKey": false, + "notNull": false + }, + "vector_1024": { + "name": "vector_1024", + "type": "halfvec(1024)", + "primaryKey": false, + "notNull": false + }, + "vector_3072": { + "name": "vector_3072", + "type": "halfvec(3072)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "embedding_search_kb_idx": { + "name": "embedding_search_kb_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "embedding_search_document_lookup_idx": { + "name": "embedding_search_document_lookup_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"embedding_search\".\"enabled\"", + "concurrently": true, + "method": "btree", + "with": {} + }, + "embedding_search_binary_hnsw_idx": { + "name": "embedding_search_binary_hnsw_idx", + "columns": [ + { + "expression": "binary", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "bit_hamming_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_search_384_binary_hnsw_idx": { + "name": "embedding_search_384_binary_hnsw_idx", + "columns": [ + { + "expression": "binary_384", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "bit_hamming_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_search_768_binary_hnsw_idx": { + "name": "embedding_search_768_binary_hnsw_idx", + "columns": [ + { + "expression": "binary_768", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "bit_hamming_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_search_1024_binary_hnsw_idx": { + "name": "embedding_search_1024_binary_hnsw_idx", + "columns": [ + { + "expression": "binary_1024", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "bit_hamming_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_search_3072_binary_hnsw_idx": { + "name": "embedding_search_3072_binary_hnsw_idx", + "columns": [ + { + "expression": "binary_3072", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "bit_hamming_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_search_cosine_hnsw_idx": { + "name": "embedding_search_cosine_hnsw_idx", + "columns": [ + { + "expression": "vector", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "halfvec_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_search_512_cosine_hnsw_idx": { + "name": "embedding_search_512_cosine_hnsw_idx", + "columns": [ + { + "expression": "vector_512", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "halfvec_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_search_384_cosine_hnsw_idx": { + "name": "embedding_search_384_cosine_hnsw_idx", + "columns": [ + { + "expression": "vector_384", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "halfvec_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_search_768_cosine_hnsw_idx": { + "name": "embedding_search_768_cosine_hnsw_idx", + "columns": [ + { + "expression": "vector_768", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "halfvec_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_search_1024_cosine_hnsw_idx": { + "name": "embedding_search_1024_cosine_hnsw_idx", + "columns": [ + { + "expression": "vector_1024", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "halfvec_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_search_3072_cosine_hnsw_idx": { + "name": "embedding_search_3072_cosine_hnsw_idx", + "columns": [ + { + "expression": "vector_3072", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "halfvec_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + } + }, + "foreignKeys": { + "embedding_search_id_embedding_id_fk": { + "name": "embedding_search_id_embedding_id_fk", + "tableFrom": "embedding_search", + "tableTo": "embedding", + "columnsFrom": ["id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "embedding_search_width_check": { + "name": "embedding_search_width_check", + "value": "num_nonnulls(\"binary\", \"binary_384\", \"binary_768\", \"binary_1024\", \"binary_3072\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.embedding_secret_provenance": { + "name": "embedding_secret_provenance", + "schema": "", + "columns": { + "embedding_id": { + "name": "embedding_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entries": { + "name": "entries", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "embedding_secret_provenance_embedding_id_embedding_id_fk": { + "name": "embedding_secret_provenance_embedding_id_embedding_id_fk", + "tableFrom": "embedding_secret_provenance", + "tableTo": "embedding", + "columnsFrom": ["embedding_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "embedding_secret_provenance_status_check": { + "name": "embedding_secret_provenance_status_check", + "value": "\"embedding_secret_provenance\".\"status\" IN ('exact', 'unknown')" + } + }, + "isRLSEnabled": false + }, + "public.environment": { + "name": "environment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "variables": { + "name": "variables", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "environment_user_id_user_id_fk": { + "name": "environment_user_id_user_id_fk", + "tableFrom": "environment", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "environment_user_id_unique": { + "name": "environment_user_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_large_value_dependencies": { + "name": "execution_large_value_dependencies", + "schema": "", + "columns": { + "parent_key": { + "name": "parent_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "child_key": { + "name": "child_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_large_value_dependencies_workspace_parent_key_idx": { + "name": "execution_large_value_dependencies_workspace_parent_key_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_large_value_dependencies_workspace_child_key_idx": { + "name": "execution_large_value_dependencies_workspace_child_key_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "child_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_large_value_dependencies_workspace_id_workspace_id_fk": { + "name": "execution_large_value_dependencies_workspace_id_workspace_id_fk", + "tableFrom": "execution_large_value_dependencies", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "execution_large_value_dependencies_parent_key_child_key_pk": { + "name": "execution_large_value_dependencies_parent_key_child_key_pk", + "columns": ["parent_key", "child_key"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_large_value_references": { + "name": "execution_large_value_references", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "execution_large_value_reference_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_large_value_references_workspace_execution_source_idx": { + "name": "execution_large_value_references_workspace_execution_source_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_large_value_references_workflow_id_idx": { + "name": "execution_large_value_references_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_large_value_references_workspace_id_workspace_id_fk": { + "name": "execution_large_value_references_workspace_id_workspace_id_fk", + "tableFrom": "execution_large_value_references", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_large_value_references_workflow_id_workflow_id_fk": { + "name": "execution_large_value_references_workflow_id_workflow_id_fk", + "tableFrom": "execution_large_value_references", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "execution_large_value_references_key_execution_id_source_pk": { + "name": "execution_large_value_references_key_execution_id_source_pk", + "columns": ["key", "execution_id", "source"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_large_values": { + "name": "execution_large_values", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_execution_id": { + "name": "owner_execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "execution_large_values_owner_execution_id_idx": { + "name": "execution_large_values_owner_execution_id_idx", + "columns": [ + { + "expression": "owner_execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_large_values_cleanup_idx": { + "name": "execution_large_values_cleanup_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"execution_large_values\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_large_values_tombstone_cleanup_idx": { + "name": "execution_large_values_tombstone_cleanup_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"execution_large_values\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_large_values_workflow_id_idx": { + "name": "execution_large_values_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_large_values_workspace_id_workspace_id_fk": { + "name": "execution_large_values_workspace_id_workspace_id_fk", + "tableFrom": "execution_large_values", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_large_values_workflow_id_workflow_id_fk": { + "name": "execution_large_values_workflow_id_workflow_id_fk", + "tableFrom": "execution_large_values", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.folder": { + "name": "folder", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "folder_resource_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "locked": { + "name": "locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "folder_user_idx": { + "name": "folder_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "folder_workspace_resource_parent_idx": { + "name": "folder_workspace_resource_parent_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "folder_parent_sort_idx": { + "name": "folder_parent_sort_idx", + "columns": [ + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "folder_deleted_at_idx": { + "name": "folder_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "folder_workspace_deleted_partial_idx": { + "name": "folder_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"folder\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "folder_workspace_resource_parent_name_active_unique": { + "name": "folder_workspace_resource_parent_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"parent_id\", '')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"folder\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "folder_user_id_user_id_fk": { + "name": "folder_user_id_user_id_fk", + "tableFrom": "folder", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "folder_workspace_id_workspace_id_fk": { + "name": "folder_workspace_id_workspace_id_fk", + "tableFrom": "folder", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "folder_parent_id_folder_id_fk": { + "name": "folder_parent_id_folder_id_fk", + "tableFrom": "folder", + "tableTo": "folder", + "columnsFrom": ["parent_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.idempotency_key": { + "name": "idempotency_key", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "result": { + "name": "result", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idempotency_key_created_at_idx": { + "name": "idempotency_key_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invitation": { + "name": "invitation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "invitation_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'organization'" + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "inviter_id": { + "name": "inviter_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "membership_intent": { + "name": "membership_intent", + "type": "invitation_membership_intent", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'internal'" + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "invitation_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "invitation_email_idx": { + "name": "invitation_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_organization_id_idx": { + "name": "invitation_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_status_idx": { + "name": "invitation_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_pending_email_org_unique": { + "name": "invitation_pending_email_org_unique", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"invitation\".\"status\" = 'pending' AND \"invitation\".\"organization_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invitation_inviter_id_user_id_fk": { + "name": "invitation_inviter_id_user_id_fk", + "tableFrom": "invitation", + "tableTo": "user", + "columnsFrom": ["inviter_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitation_organization_id_organization_id_fk": { + "name": "invitation_organization_id_organization_id_fk", + "tableFrom": "invitation", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "invitation_token_unique": { + "name": "invitation_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invitation_workspace_grant": { + "name": "invitation_workspace_grant", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "invitation_id": { + "name": "invitation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission": { + "name": "permission", + "type": "permission_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "invitation_workspace_grant_unique": { + "name": "invitation_workspace_grant_unique", + "columns": [ + { + "expression": "invitation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_workspace_grant_workspace_id_idx": { + "name": "invitation_workspace_grant_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invitation_workspace_grant_invitation_id_invitation_id_fk": { + "name": "invitation_workspace_grant_invitation_id_invitation_id_fk", + "tableFrom": "invitation_workspace_grant", + "tableTo": "invitation", + "columnsFrom": ["invitation_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitation_workspace_grant_workspace_id_workspace_id_fk": { + "name": "invitation_workspace_grant_workspace_id_workspace_id_fk", + "tableFrom": "invitation_workspace_grant", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.job_execution_logs": { + "name": "job_execution_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "schedule_id": { + "name": "schedule_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "total_duration_ms": { + "name": "total_duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "execution_data": { + "name": "execution_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "cost": { + "name": "cost", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "job_execution_logs_schedule_id_idx": { + "name": "job_execution_logs_schedule_id_idx", + "columns": [ + { + "expression": "schedule_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "job_execution_logs_workspace_started_at_idx": { + "name": "job_execution_logs_workspace_started_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "job_execution_logs_workspace_ended_at_id_idx": { + "name": "job_execution_logs_workspace_ended_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"ended_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "job_execution_logs_execution_id_unique": { + "name": "job_execution_logs_execution_id_unique", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "job_execution_logs_trigger_idx": { + "name": "job_execution_logs_trigger_idx", + "columns": [ + { + "expression": "trigger", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "job_execution_logs_schedule_id_workflow_schedule_id_fk": { + "name": "job_execution_logs_schedule_id_workflow_schedule_id_fk", + "tableFrom": "job_execution_logs", + "tableTo": "workflow_schedule", + "columnsFrom": ["schedule_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "job_execution_logs_workspace_id_workspace_id_fk": { + "name": "job_execution_logs_workspace_id_workspace_id_fk", + "tableFrom": "job_execution_logs", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_base": { + "name": "knowledge_base", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "folder_id": { + "name": "folder_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_search_index": { + "name": "is_search_index", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "embedding_model": { + "name": "embedding_model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text-embedding-3-small'" + }, + "embedding_dimension": { + "name": "embedding_dimension", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1536 + }, + "chunking_config": { + "name": "chunking_config", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{\"maxSize\": 1024, \"minSize\": 1, \"overlap\": 200}'" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "kb_organization_id_idx": { + "name": "kb_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_organization_search_index_unique": { + "name": "kb_organization_search_index_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"knowledge_base\".\"is_search_index\" = true AND \"knowledge_base\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_organization_name_active_unique": { + "name": "kb_organization_name_active_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"knowledge_base\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_user_id_idx": { + "name": "kb_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_workspace_id_idx": { + "name": "kb_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_user_workspace_idx": { + "name": "kb_user_workspace_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_folder_id_idx": { + "name": "kb_folder_id_idx", + "columns": [ + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_deleted_at_idx": { + "name": "kb_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_workspace_deleted_partial_idx": { + "name": "kb_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"knowledge_base\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_workspace_name_active_unique": { + "name": "kb_workspace_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"knowledge_base\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_workspace_search_index_unique": { + "name": "kb_workspace_search_index_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"knowledge_base\".\"is_search_index\" = true AND \"knowledge_base\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_base_user_id_user_id_fk": { + "name": "knowledge_base_user_id_user_id_fk", + "tableFrom": "knowledge_base", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knowledge_base_workspace_id_workspace_id_fk": { + "name": "knowledge_base_workspace_id_workspace_id_fk", + "tableFrom": "knowledge_base", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knowledge_base_organization_id_organization_id_fk": { + "name": "knowledge_base_organization_id_organization_id_fk", + "tableFrom": "knowledge_base", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knowledge_base_folder_id_folder_id_fk": { + "name": "knowledge_base_folder_id_folder_id_fk", + "tableFrom": "knowledge_base", + "tableTo": "folder", + "columnsFrom": ["folder_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kb_owner_check": { + "name": "kb_owner_check", + "value": "num_nonnulls(\"knowledge_base\".\"workspace_id\", \"knowledge_base\".\"organization_id\") = 1" + }, + "kb_organization_search_index_check": { + "name": "kb_organization_search_index_check", + "value": "\"knowledge_base\".\"organization_id\" IS NULL OR \"knowledge_base\".\"is_search_index\"" + }, + "kb_organization_folder_check": { + "name": "kb_organization_folder_check", + "value": "\"knowledge_base\".\"organization_id\" IS NULL OR \"knowledge_base\".\"folder_id\" IS NULL" + } + }, + "isRLSEnabled": false + }, + "public.knowledge_base_tag_definitions": { + "name": "knowledge_base_tag_definitions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tag_slot": { + "name": "tag_slot", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "field_type": { + "name": "field_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "kb_tag_definitions_kb_slot_idx": { + "name": "kb_tag_definitions_kb_slot_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tag_slot", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_tag_definitions_kb_display_name_idx": { + "name": "kb_tag_definitions_kb_display_name_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_tag_definitions_kb_id_idx": { + "name": "kb_tag_definitions_kb_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_base_tag_definitions_knowledge_base_id_knowledge_base_id_fk": { + "name": "knowledge_base_tag_definitions_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "knowledge_base_tag_definitions", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_connector": { + "name": "knowledge_connector", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connector_type": { + "name": "connector_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_api_key": { + "name": "encrypted_api_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_config": { + "name": "source_config", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "sync_mode": { + "name": "sync_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'full'" + }, + "sync_interval_minutes": { + "name": "sync_interval_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1440 + }, + "access_mode": { + "name": "access_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'workspace'" + }, + "credential_group_id": { + "name": "credential_group_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_group_option_id": { + "name": "credential_group_option_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "member_sync_status": { + "name": "member_sync_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "member_sync_lock_token": { + "name": "member_sync_lock_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "member_sync_lock_lease_at": { + "name": "member_sync_lock_lease_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "next_member_sync_at": { + "name": "next_member_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_member_sync_at": { + "name": "last_member_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_member_sync_error": { + "name": "last_member_sync_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "member_sync_consecutive_failures": { + "name": "member_sync_consecutive_failures", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "access_rewrite_pending": { + "name": "access_rewrite_pending", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_sync_error": { + "name": "last_sync_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_sync_doc_count": { + "name": "last_sync_doc_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "listing_checkpoint": { + "name": "listing_checkpoint", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "directory_checkpoint": { + "name": "directory_checkpoint", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "next_sync_at": { + "name": "next_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "next_directory_sync_at": { + "name": "next_directory_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "consecutive_failures": { + "name": "consecutive_failures", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "sync_lock_token": { + "name": "sync_lock_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sync_lock_lease_at": { + "name": "sync_lock_lease_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "kc_knowledge_base_id_idx": { + "name": "kc_knowledge_base_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kc_status_next_sync_idx": { + "name": "kc_status_next_sync_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_sync_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kc_archived_at_partial_idx": { + "name": "kc_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"knowledge_connector\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "kc_deleted_at_partial_idx": { + "name": "kc_deleted_at_partial_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"knowledge_connector\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "kc_member_sync_due_idx": { + "name": "kc_member_sync_due_idx", + "columns": [ + { + "expression": "member_sync_status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_member_sync_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"knowledge_connector\".\"access_mode\" = 'members' AND \"knowledge_connector\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "kc_directory_sync_due_idx": { + "name": "kc_directory_sync_due_idx", + "columns": [ + { + "expression": "next_directory_sync_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"knowledge_connector\".\"access_mode\" = 'admin' AND \"knowledge_connector\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_connector_knowledge_base_id_knowledge_base_id_fk": { + "name": "knowledge_connector_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "knowledge_connector", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knowledge_connector_credential_group_id_credential_group_id_fk": { + "name": "knowledge_connector_credential_group_id_credential_group_id_fk", + "tableFrom": "knowledge_connector", + "tableTo": "credential_group", + "columnsFrom": ["credential_group_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kc_access_mode_check": { + "name": "kc_access_mode_check", + "value": "\"knowledge_connector\".\"access_mode\" IN ('workspace', 'members', 'admin')" + }, + "kc_member_sync_status_check": { + "name": "kc_member_sync_status_check", + "value": "\"knowledge_connector\".\"member_sync_status\" IN ('idle', 'pending', 'running', 'error', 'disabled')" + }, + "kc_sync_lock_exclusive_check": { + "name": "kc_sync_lock_exclusive_check", + "value": "NOT (\"knowledge_connector\".\"sync_lock_token\" IS NOT NULL AND \"knowledge_connector\".\"member_sync_lock_token\" IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.knowledge_connector_member": { + "name": "knowledge_connector_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_token": { + "name": "subject_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "consecutive_failures": { + "name": "consecutive_failures", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_started_at": { + "name": "last_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_complete_listing_at": { + "name": "last_complete_listing_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_listed_count": { + "name": "last_listed_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "member_synced_through": { + "name": "member_synced_through", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "change_cursor": { + "name": "change_cursor", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "listing_checkpoint": { + "name": "listing_checkpoint", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "suspended_at": { + "name": "suspended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "kcm_organization_id_idx": { + "name": "kcm_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kcm_connector_credential_unique": { + "name": "kcm_connector_credential_unique", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kcm_connector_queue_idx": { + "name": "kcm_connector_queue_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "first" + }, + { + "expression": "last_started_at", + "isExpression": false, + "asc": true, + "nulls": "first" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kcm_credential_idx": { + "name": "kcm_credential_idx", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_connector_member_workspace_id_workspace_id_fk": { + "name": "knowledge_connector_member_workspace_id_workspace_id_fk", + "tableFrom": "knowledge_connector_member", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knowledge_connector_member_organization_id_organization_id_fk": { + "name": "knowledge_connector_member_organization_id_organization_id_fk", + "tableFrom": "knowledge_connector_member", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knowledge_connector_member_connector_id_knowledge_connector_id_fk": { + "name": "knowledge_connector_member_connector_id_knowledge_connector_id_fk", + "tableFrom": "knowledge_connector_member", + "tableTo": "knowledge_connector", + "columnsFrom": ["connector_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knowledge_connector_member_credential_id_credential_id_fk": { + "name": "knowledge_connector_member_credential_id_credential_id_fk", + "tableFrom": "knowledge_connector_member", + "tableTo": "credential", + "columnsFrom": ["credential_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kcm_owner_check": { + "name": "kcm_owner_check", + "value": "num_nonnulls(\"knowledge_connector_member\".\"workspace_id\", \"knowledge_connector_member\".\"organization_id\") = 1" + }, + "kcm_status_check": { + "name": "kcm_status_check", + "value": "\"knowledge_connector_member\".\"status\" IN ('active', 'suspended', 'disabled')" + }, + "kcm_subject_token_shape_check": { + "name": "kcm_subject_token_shape_check", + "value": "\"knowledge_connector_member\".\"subject_token\" ~ '^s:[^:]+:[^:]+:.+$'" + } + }, + "isRLSEnabled": false + }, + "public.knowledge_connector_member_sync_log": { + "name": "knowledge_connector_member_sync_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "members_claimed": { + "name": "members_claimed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "members_completed": { + "name": "members_completed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "members_incomplete": { + "name": "members_incomplete", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "members_failed": { + "name": "members_failed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_failed": { + "name": "docs_failed", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "processing_dispatch_failed": { + "name": "processing_dispatch_failed", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "docs_listed": { + "name": "docs_listed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_added": { + "name": "docs_added", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_updated": { + "name": "docs_updated", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_unchanged": { + "name": "docs_unchanged", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_hydrated_once": { + "name": "docs_hydrated_once", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "observations_added": { + "name": "observations_added", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "observations_removed": { + "name": "observations_removed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_tombstoned": { + "name": "docs_tombstoned", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_resurrected": { + "name": "docs_resurrected", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_purged": { + "name": "docs_purged", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "credentials_audited": { + "name": "credentials_audited", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "kcmsl_connector_started_at_idx": { + "name": "kcmsl_connector_started_at_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"started_at\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kcmsl_started_at_partial_idx": { + "name": "kcmsl_started_at_partial_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"knowledge_connector_member_sync_log\".\"status\" = 'started'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_connector_member_sync_log_connector_id_knowledge_connector_id_fk": { + "name": "knowledge_connector_member_sync_log_connector_id_knowledge_connector_id_fk", + "tableFrom": "knowledge_connector_member_sync_log", + "tableTo": "knowledge_connector", + "columnsFrom": ["connector_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kcmsl_status_check": { + "name": "kcmsl_status_check", + "value": "\"knowledge_connector_member_sync_log\".\"status\" IN ('started', 'partial', 'completed', 'failed')" + } + }, + "isRLSEnabled": false + }, + "public.knowledge_connector_partition": { + "name": "knowledge_connector_partition", + "schema": "", + "columns": { + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "partition_key": { + "name": "partition_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "generation_id": { + "name": "generation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "context": { + "name": "context", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "cursor": { + "name": "cursor", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "retry_at": { + "name": "retry_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_served_at": { + "name": "last_served_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "failure": { + "name": "failure", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "permission_cursor": { + "name": "permission_cursor", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "permission_attempts": { + "name": "permission_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "permission_retry_at": { + "name": "permission_retry_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "permission_last_served_at": { + "name": "permission_last_served_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "permission_started_at": { + "name": "permission_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "permission_failure": { + "name": "permission_failure", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "kcp_content_due_idx": { + "name": "kcp_content_due_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "generation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "retry_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_served_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kcp_permission_due_idx": { + "name": "kcp_permission_due_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "generation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_retry_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_last_served_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_connector_partition_connector_id_knowledge_connector_id_fk": { + "name": "knowledge_connector_partition_connector_id_knowledge_connector_id_fk", + "tableFrom": "knowledge_connector_partition", + "tableTo": "knowledge_connector", + "columnsFrom": ["connector_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "kcp_pk": { + "name": "kcp_pk", + "columns": ["connector_id", "partition_key"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kcp_partition_key_check": { + "name": "kcp_partition_key_check", + "value": "octet_length(\"knowledge_connector_partition\".\"partition_key\") BETWEEN 1 AND 1024" + }, + "kcp_context_check": { + "name": "kcp_context_check", + "value": "jsonb_typeof(\"knowledge_connector_partition\".\"context\") = 'object' AND octet_length(\"knowledge_connector_partition\".\"context\"::text) <= 16384" + }, + "kcp_status_check": { + "name": "kcp_status_check", + "value": "\"knowledge_connector_partition\".\"status\" IN ('pending', 'complete', 'blocked')" + }, + "kcp_cursor_check": { + "name": "kcp_cursor_check", + "value": "(\"knowledge_connector_partition\".\"cursor\" IS NULL OR octet_length(\"knowledge_connector_partition\".\"cursor\") <= 393216) AND (\"knowledge_connector_partition\".\"permission_cursor\" IS NULL OR octet_length(\"knowledge_connector_partition\".\"permission_cursor\") <= 393216)" + }, + "kcp_attempts_check": { + "name": "kcp_attempts_check", + "value": "\"knowledge_connector_partition\".\"attempts\" >= 0 AND \"knowledge_connector_partition\".\"permission_attempts\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.knowledge_connector_permission_grant": { + "name": "knowledge_connector_permission_grant", + "schema": "", + "columns": { + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "group_key": { + "name": "group_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_token": { + "name": "subject_token", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "kcpg_subject_idx": { + "name": "kcpg_subject_idx", + "columns": [ + { + "expression": "subject_token", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "group_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kcpg_snapshot_fk": { + "name": "kcpg_snapshot_fk", + "tableFrom": "knowledge_connector_permission_grant", + "tableTo": "knowledge_connector_permission_snapshot", + "columnsFrom": ["connector_id"], + "columnsTo": ["connector_id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "kcpg_pk": { + "name": "kcpg_pk", + "columns": ["connector_id", "group_key", "subject_token"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kcpg_group_check": { + "name": "kcpg_group_check", + "value": "length(\"knowledge_connector_permission_grant\".\"group_key\") BETWEEN 1 AND 255" + }, + "kcpg_subject_check": { + "name": "kcpg_subject_check", + "value": "\"knowledge_connector_permission_grant\".\"subject_token\" ~ '^u:[^[:space:]A-Z]+@[^[:space:]A-Z]+$'" + } + }, + "isRLSEnabled": false + }, + "public.knowledge_connector_permission_snapshot": { + "name": "knowledge_connector_permission_snapshot", + "schema": "", + "columns": { + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "kcps_connector_fk": { + "name": "kcps_connector_fk", + "tableFrom": "knowledge_connector_permission_snapshot", + "tableTo": "knowledge_connector", + "columnsFrom": ["connector_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kcps_revision_check": { + "name": "kcps_revision_check", + "value": "\"knowledge_connector_permission_snapshot\".\"revision\" > 0" + } + }, + "isRLSEnabled": false + }, + "public.knowledge_connector_sync_log": { + "name": "knowledge_connector_sync_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "docs_added": { + "name": "docs_added", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_updated": { + "name": "docs_updated", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_deleted": { + "name": "docs_deleted", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_unchanged": { + "name": "docs_unchanged", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_skipped": { + "name": "docs_skipped", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_failed": { + "name": "docs_failed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "listed_count": { + "name": "listed_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "kcsl_connector_started_at_idx": { + "name": "kcsl_connector_started_at_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"started_at\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kcsl_started_at_partial_idx": { + "name": "kcsl_started_at_partial_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"knowledge_connector_sync_log\".\"status\" = 'started'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_connector_sync_log_connector_id_knowledge_connector_id_fk": { + "name": "knowledge_connector_sync_log_connector_id_knowledge_connector_id_fk", + "tableFrom": "knowledge_connector_sync_log", + "tableTo": "knowledge_connector", + "columnsFrom": ["connector_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_document_observation": { + "name": "knowledge_document_observation", + "schema": "", + "columns": { + "document_id": { + "name": "document_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "member_id": { + "name": "member_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "kdo_member_idx": { + "name": "kdo_member_idx", + "columns": [ + { + "expression": "member_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_document_observation_document_id_document_id_fk": { + "name": "knowledge_document_observation_document_id_document_id_fk", + "tableFrom": "knowledge_document_observation", + "tableTo": "document", + "columnsFrom": ["document_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knowledge_document_observation_member_id_knowledge_connector_member_id_fk": { + "name": "knowledge_document_observation_member_id_knowledge_connector_member_id_fk", + "tableFrom": "knowledge_document_observation", + "tableTo": "knowledge_connector_member", + "columnsFrom": ["member_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "knowledge_document_observation_document_id_member_id_pk": { + "name": "knowledge_document_observation_document_id_member_id_pk", + "columns": ["document_id", "member_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_external_directory": { + "name": "knowledge_external_directory", + "schema": "", + "columns": { + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sync_lock_token": { + "name": "sync_lock_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sync_lock_lease_at": { + "name": "sync_lock_lease_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_started_at": { + "name": "last_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_complete_sync_at": { + "name": "last_complete_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "ked_organization_id_idx": { + "name": "ked_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ked_workspace_identity_unique": { + "name": "ked_workspace_identity_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ked_organization_identity_unique": { + "name": "ked_organization_identity_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_external_directory_workspace_id_workspace_id_fk": { + "name": "knowledge_external_directory_workspace_id_workspace_id_fk", + "tableFrom": "knowledge_external_directory", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knowledge_external_directory_organization_id_organization_id_fk": { + "name": "knowledge_external_directory_organization_id_organization_id_fk", + "tableFrom": "knowledge_external_directory", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "ked_owner_check": { + "name": "ked_owner_check", + "value": "num_nonnulls(\"knowledge_external_directory\".\"workspace_id\", \"knowledge_external_directory\".\"organization_id\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.knowledge_external_group": { + "name": "knowledge_external_group", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_group_id": { + "name": "external_group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "keg_organization_id_idx": { + "name": "keg_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "keg_organization_identity_unique": { + "name": "keg_organization_identity_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "keg_organization_synced_idx": { + "name": "keg_organization_synced_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_synced_at", + "isExpression": false, + "asc": true, + "nulls": "first" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "keg_identity_unique": { + "name": "keg_identity_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "keg_workspace_synced_idx": { + "name": "keg_workspace_synced_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_synced_at", + "isExpression": false, + "asc": true, + "nulls": "first" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_external_group_organization_id_organization_id_fk": { + "name": "knowledge_external_group_organization_id_organization_id_fk", + "tableFrom": "knowledge_external_group", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "keg_workspace_fk": { + "name": "keg_workspace_fk", + "tableFrom": "knowledge_external_group", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "keg_owner_check": { + "name": "keg_owner_check", + "value": "num_nonnulls(\"knowledge_external_group\".\"workspace_id\", \"knowledge_external_group\".\"organization_id\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.knowledge_external_group_member": { + "name": "knowledge_external_group_member", + "schema": "", + "columns": { + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_token": { + "name": "subject_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "kegm_subject_token_idx": { + "name": "kegm_subject_token_idx", + "columns": [ + { + "expression": "subject_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kegm_group_fk": { + "name": "kegm_group_fk", + "tableFrom": "knowledge_external_group_member", + "tableTo": "knowledge_external_group", + "columnsFrom": ["group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "knowledge_external_group_member_group_id_subject_token_pk": { + "name": "knowledge_external_group_member_group_id_subject_token_pk", + "columns": ["group_id", "subject_token"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_server_oauth": { + "name": "mcp_server_oauth", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "mcp_server_id": { + "name": "mcp_server_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_information": { + "name": "client_information", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tokens": { + "name": "tokens", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "code_verifier": { + "name": "code_verifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state_created_at": { + "name": "state_created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_refreshed_at": { + "name": "last_refreshed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mcp_server_oauth_server_unique": { + "name": "mcp_server_oauth_server_unique", + "columns": [ + { + "expression": "mcp_server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_server_oauth_state_idx": { + "name": "mcp_server_oauth_state_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_server_oauth_mcp_server_id_mcp_servers_id_fk": { + "name": "mcp_server_oauth_mcp_server_id_mcp_servers_id_fk", + "tableFrom": "mcp_server_oauth", + "tableTo": "mcp_servers", + "columnsFrom": ["mcp_server_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_server_oauth_user_id_user_id_fk": { + "name": "mcp_server_oauth_user_id_user_id_fk", + "tableFrom": "mcp_server_oauth", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "mcp_server_oauth_workspace_id_workspace_id_fk": { + "name": "mcp_server_oauth_workspace_id_workspace_id_fk", + "tableFrom": "mcp_server_oauth", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_server_oauth_organization_id_organization_id_fk": { + "name": "mcp_server_oauth_organization_id_organization_id_fk", + "tableFrom": "mcp_server_oauth", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "mcp_server_oauth_owner_check": { + "name": "mcp_server_oauth_owner_check", + "value": "num_nonnulls(\"mcp_server_oauth\".\"workspace_id\", \"mcp_server_oauth\".\"organization_id\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.mcp_servers": { + "name": "mcp_servers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_group_id": { + "name": "credential_group_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "managed_connector_id": { + "name": "managed_connector_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oauth_config_version": { + "name": "oauth_config_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "transport": { + "name": "transport", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'headers'" + }, + "oauth_client_id": { + "name": "oauth_client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oauth_client_secret": { + "name": "oauth_client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "headers": { + "name": "headers", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "timeout": { + "name": "timeout", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 30000 + }, + "retries": { + "name": "retries", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3 + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_connected": { + "name": "last_connected", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "connection_status": { + "name": "connection_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'disconnected'" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_config": { + "name": "status_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "tool_count": { + "name": "tool_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "last_tools_refresh": { + "name": "last_tools_refresh", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "total_requests": { + "name": "total_requests", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "last_used": { + "name": "last_used", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mcp_servers_organization_id_idx": { + "name": "mcp_servers_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_servers_workspace_enabled_idx": { + "name": "mcp_servers_workspace_enabled_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_servers_credential_group_idx": { + "name": "mcp_servers_credential_group_idx", + "columns": [ + { + "expression": "credential_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_servers_credential_group_managed_connector_unique": { + "name": "mcp_servers_credential_group_managed_connector_unique", + "columns": [ + { + "expression": "credential_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "managed_connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"mcp_servers\".\"credential_group_id\" IS NOT NULL AND \"mcp_servers\".\"managed_connector_id\" IS NOT NULL AND \"mcp_servers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_servers_workspace_deleted_partial_idx": { + "name": "mcp_servers_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"mcp_servers\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_servers_workspace_id_workspace_id_fk": { + "name": "mcp_servers_workspace_id_workspace_id_fk", + "tableFrom": "mcp_servers", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_servers_organization_id_organization_id_fk": { + "name": "mcp_servers_organization_id_organization_id_fk", + "tableFrom": "mcp_servers", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_servers_credential_group_id_credential_group_id_fk": { + "name": "mcp_servers_credential_group_id_credential_group_id_fk", + "tableFrom": "mcp_servers", + "tableTo": "credential_group", + "columnsFrom": ["credential_group_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "mcp_servers_created_by_user_id_fk": { + "name": "mcp_servers_created_by_user_id_fk", + "tableFrom": "mcp_servers", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "mcp_servers_owner_check": { + "name": "mcp_servers_owner_check", + "value": "num_nonnulls(\"mcp_servers\".\"workspace_id\", \"mcp_servers\".\"organization_id\") = 1" + }, + "mcp_servers_organization_managed_check": { + "name": "mcp_servers_organization_managed_check", + "value": "\"mcp_servers\".\"organization_id\" IS NULL OR \"mcp_servers\".\"credential_group_id\" IS NOT NULL" + }, + "mcp_servers_credential_group_managed_connector_check": { + "name": "mcp_servers_credential_group_managed_connector_check", + "value": "\"mcp_servers\".\"credential_group_id\" IS NULL OR \"mcp_servers\".\"managed_connector_id\" IS NOT NULL" + }, + "mcp_servers_managed_connector_oauth_check": { + "name": "mcp_servers_managed_connector_oauth_check", + "value": "\"mcp_servers\".\"managed_connector_id\" IS NULL OR \"mcp_servers\".\"auth_type\" = 'oauth'" + } + }, + "isRLSEnabled": false + }, + "public.member": { + "name": "member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "member_user_id_unique": { + "name": "member_user_id_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "member_organization_id_idx": { + "name": "member_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "member_user_id_user_id_fk": { + "name": "member_user_id_user_id_fk", + "tableFrom": "member", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "member_organization_id_organization_id_fk": { + "name": "member_organization_id_organization_id_fk", + "tableFrom": "member", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.memory": { + "name": "memory", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "secret_provenance_version": { + "name": "secret_provenance_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "memory_key_idx": { + "name": "memory_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workspace_idx": { + "name": "memory_workspace_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workspace_key_idx": { + "name": "memory_workspace_key_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workspace_deleted_partial_idx": { + "name": "memory_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"memory\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "memory_workspace_id_workspace_id_fk": { + "name": "memory_workspace_id_workspace_id_fk", + "tableFrom": "memory", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.memory_secret_provenance": { + "name": "memory_secret_provenance", + "schema": "", + "columns": { + "memory_id": { + "name": "memory_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entries": { + "name": "entries", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "memory_secret_provenance_memory_id_memory_id_fk": { + "name": "memory_secret_provenance_memory_id_memory_id_fk", + "tableFrom": "memory_secret_provenance", + "tableTo": "memory", + "columnsFrom": ["memory_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "memory_secret_provenance_status_check": { + "name": "memory_secret_provenance_status_check", + "value": "\"memory_secret_provenance\".\"status\" IN ('exact', 'unknown')" + } + }, + "isRLSEnabled": false + }, + "public.mothership_inbox_allowed_sender": { + "name": "mothership_inbox_allowed_sender", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "added_by": { + "name": "added_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "inbox_sender_ws_email_idx": { + "name": "inbox_sender_ws_email_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mothership_inbox_allowed_sender_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_allowed_sender_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_allowed_sender", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mothership_inbox_allowed_sender_added_by_user_id_fk": { + "name": "mothership_inbox_allowed_sender_added_by_user_id_fk", + "tableFrom": "mothership_inbox_allowed_sender", + "tableTo": "user", + "columnsFrom": ["added_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_inbox_task": { + "name": "mothership_inbox_task", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_email": { + "name": "from_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_name": { + "name": "from_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body_preview": { + "name": "body_preview", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body_text": { + "name": "body_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body_html": { + "name": "body_html", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_message_id": { + "name": "email_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "in_reply_to": { + "name": "in_reply_to", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "response_message_id": { + "name": "response_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agentmail_message_id": { + "name": "agentmail_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "trigger_job_id": { + "name": "trigger_job_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_summary": { + "name": "result_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rejection_reason": { + "name": "rejection_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "has_attachments": { + "name": "has_attachments", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cc_recipients": { + "name": "cc_recipients", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "processing_started_at": { + "name": "processing_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "inbox_task_ws_created_at_idx": { + "name": "inbox_task_ws_created_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_task_ws_status_idx": { + "name": "inbox_task_ws_status_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_task_response_msg_id_idx": { + "name": "inbox_task_response_msg_id_idx", + "columns": [ + { + "expression": "response_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_task_email_msg_id_idx": { + "name": "inbox_task_email_msg_id_idx", + "columns": [ + { + "expression": "email_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mothership_inbox_task_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_task_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_task", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mothership_inbox_task_chat_id_copilot_chats_id_fk": { + "name": "mothership_inbox_task_chat_id_copilot_chats_id_fk", + "tableFrom": "mothership_inbox_task", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_inbox_webhook": { + "name": "mothership_inbox_webhook", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "webhook_id": { + "name": "webhook_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "mothership_inbox_webhook_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_webhook_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_webhook", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mothership_inbox_webhook_workspace_id_unique": { + "name": "mothership_inbox_webhook_workspace_id_unique", + "nullsNotDistinct": false, + "columns": ["workspace_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_settings": { + "name": "mothership_settings", + "schema": "", + "columns": { + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "mcp_tool_refs": { + "name": "mcp_tool_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "custom_tool_refs": { + "name": "custom_tool_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "skill_refs": { + "name": "skill_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "mothership_settings_workspace_id_workspace_id_fk": { + "name": "mothership_settings_workspace_id_workspace_id_fk", + "tableFrom": "mothership_settings", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_access_token": { + "name": "oauth_access_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_id": { + "name": "refresh_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "resource": { + "name": "resource", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "oauth_access_token_client_id_idx": { + "name": "oauth_access_token_client_id_idx", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_access_token_session_id_idx": { + "name": "oauth_access_token_session_id_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_access_token_refresh_id_idx": { + "name": "oauth_access_token_refresh_id_idx", + "columns": [ + { + "expression": "refresh_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_access_token_user_client_idx": { + "name": "oauth_access_token_user_client_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_access_token_expires_at_idx": { + "name": "oauth_access_token_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oauth_access_token_client_id_oauth_client_client_id_fk": { + "name": "oauth_access_token_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "oauth_client", + "columnsFrom": ["client_id"], + "columnsTo": ["client_id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_access_token_session_id_session_id_fk": { + "name": "oauth_access_token_session_id_session_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "session", + "columnsFrom": ["session_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "oauth_access_token_user_id_user_id_fk": { + "name": "oauth_access_token_user_id_user_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_access_token_refresh_id_oauth_refresh_token_id_fk": { + "name": "oauth_access_token_refresh_id_oauth_refresh_token_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "oauth_refresh_token", + "columnsFrom": ["refresh_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_access_token_token_unique": { + "name": "oauth_access_token_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": { + "oauth_access_token_search_resource_check": { + "name": "oauth_access_token_search_resource_check", + "value": "NOT ('search:read' = ANY(\"oauth_access_token\".\"scopes\")) OR \"oauth_access_token\".\"resource\" IS NOT NULL" + } + }, + "isRLSEnabled": false + }, + "public.oauth_client": { + "name": "oauth_client", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "disabled": { + "name": "disabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "skip_consent": { + "name": "skip_consent", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "enable_end_session": { + "name": "enable_end_session", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "subject_type": { + "name": "subject_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uri": { + "name": "uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "contacts": { + "name": "contacts", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "tos": { + "name": "tos", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "policy": { + "name": "policy", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_id": { + "name": "software_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_version": { + "name": "software_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_statement": { + "name": "software_statement", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redirect_uris": { + "name": "redirect_uris", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "post_logout_redirect_uris": { + "name": "post_logout_redirect_uris", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "token_endpoint_auth_method": { + "name": "token_endpoint_auth_method", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "grant_types": { + "name": "grant_types", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "response_types": { + "name": "response_types", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "require_pkce": { + "name": "require_pkce", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "oauth_client_user_id_idx": { + "name": "oauth_client_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oauth_client_user_id_user_id_fk": { + "name": "oauth_client_user_id_user_id_fk", + "tableFrom": "oauth_client", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_client_client_id_unique": { + "name": "oauth_client_client_id_unique", + "nullsNotDistinct": false, + "columns": ["client_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_consent": { + "name": "oauth_consent", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "oauth_consent_client_id_idx": { + "name": "oauth_consent_client_id_idx", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oauth_consent_client_id_oauth_client_client_id_fk": { + "name": "oauth_consent_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_consent", + "tableTo": "oauth_client", + "columnsFrom": ["client_id"], + "columnsTo": ["client_id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_consent_user_id_user_id_fk": { + "name": "oauth_consent_user_id_user_id_fk", + "tableFrom": "oauth_consent", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_consent_user_client_reference_unique": { + "name": "oauth_consent_user_client_reference_unique", + "nullsNotDistinct": true, + "columns": ["user_id", "client_id", "reference_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_refresh_token": { + "name": "oauth_refresh_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "revoked": { + "name": "revoked", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "auth_time": { + "name": "auth_time", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "resource": { + "name": "resource", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "family_id": { + "name": "family_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "oauth_refresh_token_client_id_idx": { + "name": "oauth_refresh_token_client_id_idx", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_refresh_token_session_id_idx": { + "name": "oauth_refresh_token_session_id_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_refresh_token_user_client_idx": { + "name": "oauth_refresh_token_user_client_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_refresh_token_expires_at_idx": { + "name": "oauth_refresh_token_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oauth_refresh_token_client_id_oauth_client_client_id_fk": { + "name": "oauth_refresh_token_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_refresh_token", + "tableTo": "oauth_client", + "columnsFrom": ["client_id"], + "columnsTo": ["client_id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_refresh_token_session_id_session_id_fk": { + "name": "oauth_refresh_token_session_id_session_id_fk", + "tableFrom": "oauth_refresh_token", + "tableTo": "session", + "columnsFrom": ["session_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "oauth_refresh_token_user_id_user_id_fk": { + "name": "oauth_refresh_token_user_id_user_id_fk", + "tableFrom": "oauth_refresh_token", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_refresh_token_family_id_oauth_token_family_id_fk": { + "name": "oauth_refresh_token_family_id_oauth_token_family_id_fk", + "tableFrom": "oauth_refresh_token", + "tableTo": "oauth_token_family", + "columnsFrom": ["family_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_refresh_token_token_unique": { + "name": "oauth_refresh_token_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + }, + "oauth_refresh_token_family_generation_unique": { + "name": "oauth_refresh_token_family_generation_unique", + "nullsNotDistinct": false, + "columns": ["family_id", "generation"] + } + }, + "policies": {}, + "checkConstraints": { + "oauth_refresh_token_generation_check": { + "name": "oauth_refresh_token_generation_check", + "value": "\"oauth_refresh_token\".\"generation\" BETWEEN 0 AND 1000" + }, + "oauth_refresh_token_search_resource_check": { + "name": "oauth_refresh_token_search_resource_check", + "value": "NOT ('search:read' = ANY(\"oauth_refresh_token\".\"scopes\")) OR \"oauth_refresh_token\".\"resource\" IS NOT NULL" + } + }, + "isRLSEnabled": false + }, + "public.oauth_token_family": { + "name": "oauth_token_family", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "consent_id": { + "name": "consent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "current_generation": { + "name": "current_generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "oauth_token_family_client_id_idx": { + "name": "oauth_token_family_client_id_idx", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_token_family_session_id_idx": { + "name": "oauth_token_family_session_id_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_token_family_user_client_idx": { + "name": "oauth_token_family_user_client_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_token_family_consent_id_idx": { + "name": "oauth_token_family_consent_id_idx", + "columns": [ + { + "expression": "consent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_token_family_expires_at_idx": { + "name": "oauth_token_family_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oauth_token_family_client_id_oauth_client_client_id_fk": { + "name": "oauth_token_family_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_token_family", + "tableTo": "oauth_client", + "columnsFrom": ["client_id"], + "columnsTo": ["client_id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_token_family_session_id_session_id_fk": { + "name": "oauth_token_family_session_id_session_id_fk", + "tableFrom": "oauth_token_family", + "tableTo": "session", + "columnsFrom": ["session_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "oauth_token_family_user_id_user_id_fk": { + "name": "oauth_token_family_user_id_user_id_fk", + "tableFrom": "oauth_token_family", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_token_family_consent_id_oauth_consent_id_fk": { + "name": "oauth_token_family_consent_id_oauth_consent_id_fk", + "tableFrom": "oauth_token_family", + "tableTo": "oauth_consent", + "columnsFrom": ["consent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "oauth_token_family_generation_check": { + "name": "oauth_token_family_generation_check", + "value": "\"oauth_token_family\".\"current_generation\" BETWEEN 0 AND 1000" + } + }, + "isRLSEnabled": false + }, + "public.organization": { + "name": "organization", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "session_policy_settings": { + "name": "session_policy_settings", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "security_policy_version": { + "name": "security_policy_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "require_sso": { + "name": "require_sso", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "whitelabel_settings": { + "name": "whitelabel_settings", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "data_retention_settings": { + "name": "data_retention_settings", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "org_usage_limit": { + "name": "org_usage_limit", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "storage_used_bytes": { + "name": "storage_used_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "limit_notifications": { + "name": "limit_notifications", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "credit_balance": { + "name": "credit_balance", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_access_request_settings": { + "name": "organization_access_request_settings", + "schema": "", + "columns": { + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "allow_requests": { + "name": "allow_requests", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "organization_access_request_settings_organization_id_organization_id_fk": { + "name": "organization_access_request_settings_organization_id_organization_id_fk", + "tableFrom": "organization_access_request_settings", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_access_request_settings_updated_by_user_id_fk": { + "name": "organization_access_request_settings_updated_by_user_id_fk", + "tableFrom": "organization_access_request_settings", + "tableTo": "user", + "columnsFrom": ["updated_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_byok_keys": { + "name": "organization_byok_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "encrypted_api_key": { + "name": "encrypted_api_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "organization_byok_organization_provider_idx": { + "name": "organization_byok_organization_provider_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "organization_byok_keys_organization_id_organization_id_fk": { + "name": "organization_byok_keys_organization_id_organization_id_fk", + "tableFrom": "organization_byok_keys", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_byok_keys_created_by_user_id_fk": { + "name": "organization_byok_keys_created_by_user_id_fk", + "tableFrom": "organization_byok_keys", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_member_usage_limit": { + "name": "organization_member_usage_limit", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "usage_limit": { + "name": "usage_limit", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "set_by": { + "name": "set_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "org_member_usage_limit_org_user_unique": { + "name": "org_member_usage_limit_org_user_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "org_member_usage_limit_organization_id_idx": { + "name": "org_member_usage_limit_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "organization_member_usage_limit_organization_id_organization_id_fk": { + "name": "organization_member_usage_limit_organization_id_organization_id_fk", + "tableFrom": "organization_member_usage_limit", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_member_usage_limit_user_id_user_id_fk": { + "name": "organization_member_usage_limit_user_id_user_id_fk", + "tableFrom": "organization_member_usage_limit", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_member_usage_limit_set_by_user_id_fk": { + "name": "organization_member_usage_limit_set_by_user_id_fk", + "tableFrom": "organization_member_usage_limit", + "tableTo": "user", + "columnsFrom": ["set_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_search_integration": { + "name": "organization_search_integration", + "schema": "", + "columns": { + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connector_type": { + "name": "connector_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "approved": { + "name": "approved", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "organization_search_integration_organization_id_organization_id_fk": { + "name": "organization_search_integration_organization_id_organization_id_fk", + "tableFrom": "organization_search_integration", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "organization_search_integration_organization_id_connector_type_pk": { + "name": "organization_search_integration_organization_id_connector_type_pk", + "columns": ["organization_id", "connector_type"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_search_invocation": { + "name": "organization_search_invocation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "surface": { + "name": "surface", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_types": { + "name": "source_types", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "result_count": { + "name": "result_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "organization_search_invocation_org_created_idx": { + "name": "organization_search_invocation_org_created_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "organization_search_invocation_user_idx": { + "name": "organization_search_invocation_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "organization_search_invocation_organization_id_organization_id_fk": { + "name": "organization_search_invocation_organization_id_organization_id_fk", + "tableFrom": "organization_search_invocation", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_search_invocation_user_id_user_id_fk": { + "name": "organization_search_invocation_user_id_user_id_fk", + "tableFrom": "organization_search_invocation", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "organization_search_invocation_result_count_bounds": { + "name": "organization_search_invocation_result_count_bounds", + "value": "\"organization_search_invocation\".\"result_count\" BETWEEN 0 AND 100" + }, + "organization_search_invocation_source_types_bounds": { + "name": "organization_search_invocation_source_types_bounds", + "value": "cardinality(\"organization_search_invocation\".\"source_types\") <= 100" + } + }, + "isRLSEnabled": false + }, + "public.organization_search_mcp_invocation": { + "name": "organization_search_mcp_invocation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "auth_kind": { + "name": "auth_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oauth_client_id": { + "name": "oauth_client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "organization_search_mcp_invocation_org_created_idx": { + "name": "organization_search_mcp_invocation_org_created_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "organization_search_mcp_invocation_user_idx": { + "name": "organization_search_mcp_invocation_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "org_search_mcp_invocation_org_fk": { + "name": "org_search_mcp_invocation_org_fk", + "tableFrom": "organization_search_mcp_invocation", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "org_search_mcp_invocation_user_fk": { + "name": "org_search_mcp_invocation_user_fk", + "tableFrom": "organization_search_mcp_invocation", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "organization_search_mcp_invocation_tool_check": { + "name": "organization_search_mcp_invocation_tool_check", + "value": "\"organization_search_mcp_invocation\".\"tool_name\" IN ('search', 'read_document', 'chat')" + }, + "organization_search_mcp_invocation_outcome_check": { + "name": "organization_search_mcp_invocation_outcome_check", + "value": "\"organization_search_mcp_invocation\".\"outcome\" IN ('success', 'error', 'cancelled', 'rate_limited')" + }, + "organization_search_mcp_invocation_duration_check": { + "name": "organization_search_mcp_invocation_duration_check", + "value": "\"organization_search_mcp_invocation\".\"duration_ms\" >= 0" + }, + "organization_search_mcp_invocation_client_name_check": { + "name": "organization_search_mcp_invocation_client_name_check", + "value": "length(\"organization_search_mcp_invocation\".\"client_name\") <= 256" + }, + "organization_search_mcp_invocation_auth_check": { + "name": "organization_search_mcp_invocation_auth_check", + "value": "(\"organization_search_mcp_invocation\".\"auth_kind\" = 'oauth_access_token' AND \"organization_search_mcp_invocation\".\"oauth_client_id\" IS NOT NULL)\n OR (\"organization_search_mcp_invocation\".\"auth_kind\" IN ('personal_api_key', 'workspace_api_key') AND \"organization_search_mcp_invocation\".\"oauth_client_id\" IS NULL AND \"organization_search_mcp_invocation\".\"client_name\" IS NULL)" + } + }, + "isRLSEnabled": false + }, + "public.outbox_event": { + "name": "outbox_event", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "max_attempts": { + "name": "max_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10 + }, + "available_at": { + "name": "available_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "locked_at": { + "name": "locked_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "outbox_event_status_available_idx": { + "name": "outbox_event_status_available_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "available_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "outbox_event_pending_type_available_idx": { + "name": "outbox_event_pending_type_available_idx", + "columns": [ + { + "expression": "event_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "available_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"outbox_event\".\"status\" = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "outbox_event_locked_at_idx": { + "name": "outbox_event_locked_at_idx", + "columns": [ + { + "expression": "locked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "outbox_event_type_created_idx": { + "name": "outbox_event_type_created_idx", + "columns": [ + { + "expression": "event_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.paused_executions": { + "name": "paused_executions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_snapshot": { + "name": "execution_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "pause_points": { + "name": "pause_points", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "total_pause_count": { + "name": "total_pause_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "resumed_count": { + "name": "resumed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "automatic_resume_retry_count": { + "name": "automatic_resume_retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paused'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "next_resume_at": { + "name": "next_resume_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "paused_executions_workflow_id_idx": { + "name": "paused_executions_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "paused_executions_status_idx": { + "name": "paused_executions_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "paused_executions_execution_id_unique": { + "name": "paused_executions_execution_id_unique", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "paused_executions_next_resume_at_idx": { + "name": "paused_executions_next_resume_at_idx", + "columns": [ + { + "expression": "next_resume_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "status = 'paused' AND next_resume_at IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "paused_executions_workflow_id_workflow_id_fk": { + "name": "paused_executions_workflow_id_workflow_id_fk", + "tableFrom": "paused_executions", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pending_credential_draft": { + "name": "pending_credential_draft", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oauth_config": { + "name": "oauth_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pending_draft_organization_id_idx": { + "name": "pending_draft_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pending_draft_user_provider_org": { + "name": "pending_draft_user_provider_org", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pending_draft_user_provider_ws": { + "name": "pending_draft_user_provider_ws", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pending_credential_draft_user_id_user_id_fk": { + "name": "pending_credential_draft_user_id_user_id_fk", + "tableFrom": "pending_credential_draft", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pending_credential_draft_workspace_id_workspace_id_fk": { + "name": "pending_credential_draft_workspace_id_workspace_id_fk", + "tableFrom": "pending_credential_draft", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pending_credential_draft_organization_id_organization_id_fk": { + "name": "pending_credential_draft_organization_id_organization_id_fk", + "tableFrom": "pending_credential_draft", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pending_credential_draft_credential_id_credential_id_fk": { + "name": "pending_credential_draft_credential_id_credential_id_fk", + "tableFrom": "pending_credential_draft", + "tableTo": "credential", + "columnsFrom": ["credential_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pending_draft_owner_check": { + "name": "pending_draft_owner_check", + "value": "num_nonnulls(\"pending_credential_draft\".\"workspace_id\", \"pending_credential_draft\".\"organization_id\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.permission_access_request": { + "name": "permission_access_request", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requester_id": { + "name": "requester_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope_key": { + "name": "scope_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_key": { + "name": "target_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target": { + "name": "target", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "target_label": { + "name": "target_label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "membership_id": { + "name": "membership_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "group_name": { + "name": "group_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "decision_reason": { + "name": "decision_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_by": { + "name": "decided_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decision": { + "name": "decision", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "decided_at": { + "name": "decided_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "permission_access_request_pending_unique": { + "name": "permission_access_request_pending_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requester_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"permission_access_request\".\"status\" = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_access_request_org_queue_idx": { + "name": "permission_access_request_org_queue_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_access_request_requester_idx": { + "name": "permission_access_request_requester_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requester_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "permission_access_request_organization_id_organization_id_fk": { + "name": "permission_access_request_organization_id_organization_id_fk", + "tableFrom": "permission_access_request", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_access_request_requester_id_user_id_fk": { + "name": "permission_access_request_requester_id_user_id_fk", + "tableFrom": "permission_access_request", + "tableTo": "user", + "columnsFrom": ["requester_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_access_request_decided_by_user_id_fk": { + "name": "permission_access_request_decided_by_user_id_fk", + "tableFrom": "permission_access_request", + "tableTo": "user", + "columnsFrom": ["decided_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "permission_access_request_status_check": { + "name": "permission_access_request_status_check", + "value": "\"permission_access_request\".\"status\" in ('pending', 'fulfilled', 'declined', 'cancelled', 'closed')" + } + }, + "isRLSEnabled": false + }, + "public.permission_group": { + "name": "permission_group", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "membership_mode": { + "name": "membership_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'inherit'" + } + }, + "indexes": { + "permission_group_created_by_idx": { + "name": "permission_group_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_group_organization_name_unique": { + "name": "permission_group_organization_name_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_group_organization_default_unique": { + "name": "permission_group_organization_default_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "is_default = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "permission_group_organization_id_organization_id_fk": { + "name": "permission_group_organization_id_organization_id_fk", + "tableFrom": "permission_group", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_created_by_user_id_fk": { + "name": "permission_group_created_by_user_id_fk", + "tableFrom": "permission_group", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permission_group_member": { + "name": "permission_group_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "permission_group_id": { + "name": "permission_group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "assigned_by": { + "name": "assigned_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assigned_at": { + "name": "assigned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "permission_group_member_group_id_idx": { + "name": "permission_group_member_group_id_idx", + "columns": [ + { + "expression": "permission_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_group_member_group_user_unique": { + "name": "permission_group_member_group_user_unique", + "columns": [ + { + "expression": "permission_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_group_member_organization_user_idx": { + "name": "permission_group_member_organization_user_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "permission_group_member_permission_group_id_permission_group_id_fk": { + "name": "permission_group_member_permission_group_id_permission_group_id_fk", + "tableFrom": "permission_group_member", + "tableTo": "permission_group", + "columnsFrom": ["permission_group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_member_organization_id_organization_id_fk": { + "name": "permission_group_member_organization_id_organization_id_fk", + "tableFrom": "permission_group_member", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_member_user_id_user_id_fk": { + "name": "permission_group_member_user_id_user_id_fk", + "tableFrom": "permission_group_member", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_member_assigned_by_user_id_fk": { + "name": "permission_group_member_assigned_by_user_id_fk", + "tableFrom": "permission_group_member", + "tableTo": "user", + "columnsFrom": ["assigned_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permission_group_workspace": { + "name": "permission_group_workspace", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "permission_group_id": { + "name": "permission_group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "permission_group_workspace_workspace_id_idx": { + "name": "permission_group_workspace_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_group_workspace_group_workspace_unique": { + "name": "permission_group_workspace_group_workspace_unique", + "columns": [ + { + "expression": "permission_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "permission_group_workspace_permission_group_id_permission_group_id_fk": { + "name": "permission_group_workspace_permission_group_id_permission_group_id_fk", + "tableFrom": "permission_group_workspace", + "tableTo": "permission_group", + "columnsFrom": ["permission_group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_workspace_workspace_id_workspace_id_fk": { + "name": "permission_group_workspace_workspace_id_workspace_id_fk", + "tableFrom": "permission_group_workspace", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_workspace_organization_id_organization_id_fk": { + "name": "permission_group_workspace_organization_id_organization_id_fk", + "tableFrom": "permission_group_workspace", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permissions": { + "name": "permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "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 + }, + "permission_type": { + "name": "permission_type", + "type": "permission_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "permissions_user_id_idx": { + "name": "permissions_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_entity_idx": { + "name": "permissions_entity_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": {} + }, + "permissions_user_entity_type_idx": { + "name": "permissions_user_entity_type_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_user_entity_permission_idx": { + "name": "permissions_user_entity_permission_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_unique_constraint": { + "name": "permissions_unique_constraint", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "permissions_user_id_user_id_fk": { + "name": "permissions_user_id_user_id_fk", + "tableFrom": "permissions", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pinned_item": { + "name": "pinned_item", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pinned_at": { + "name": "pinned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pinned_item_user_workspace_idx": { + "name": "pinned_item_user_workspace_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pinned_item_resource_idx": { + "name": "pinned_item_resource_idx", + "columns": [ + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pinned_item_user_resource_unique": { + "name": "pinned_item_user_resource_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pinned_item_user_id_user_id_fk": { + "name": "pinned_item_user_id_user_id_fk", + "tableFrom": "pinned_item", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pinned_item_workspace_id_workspace_id_fk": { + "name": "pinned_item_workspace_id_workspace_id_fk", + "tableFrom": "pinned_item", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.public_share": { + "name": "public_share", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'public'" + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "allowed_emails": { + "name": "allowed_emails", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "public_share_token_unique": { + "name": "public_share_token_unique", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "public_share_resource_unique": { + "name": "public_share_resource_unique", + "columns": [ + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "public_share_resource_id_idx": { + "name": "public_share_resource_id_idx", + "columns": [ + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "public_share_workspace_id_idx": { + "name": "public_share_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "public_share_workspace_id_workspace_id_fk": { + "name": "public_share_workspace_id_workspace_id_fk", + "tableFrom": "public_share", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "public_share_created_by_user_id_fk": { + "name": "public_share_created_by_user_id_fk", + "tableFrom": "public_share", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.rate_limit_bucket": { + "name": "rate_limit_bucket", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tokens": { + "name": "tokens", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "last_refill_at": { + "name": "last_refill_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "blocked_until": { + "name": "blocked_until", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "capacity_state": { + "name": "capacity_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.resource_policy": { + "name": "resource_policy", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "document": { + "name": "document", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "resource_policy_organization_id_idx": { + "name": "resource_policy_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "resource_policy_resource_unique": { + "name": "resource_policy_resource_unique", + "columns": [ + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "resource_policy_workspace_id_idx": { + "name": "resource_policy_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "resource_policy_workspace_id_workspace_id_fk": { + "name": "resource_policy_workspace_id_workspace_id_fk", + "tableFrom": "resource_policy", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "resource_policy_organization_id_organization_id_fk": { + "name": "resource_policy_organization_id_organization_id_fk", + "tableFrom": "resource_policy", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "resource_policy_created_by_user_id_fk": { + "name": "resource_policy_created_by_user_id_fk", + "tableFrom": "resource_policy", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "resource_policy_updated_by_user_id_fk": { + "name": "resource_policy_updated_by_user_id_fk", + "tableFrom": "resource_policy", + "tableTo": "user", + "columnsFrom": ["updated_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "resource_policy_owner_check": { + "name": "resource_policy_owner_check", + "value": "num_nonnulls(\"resource_policy\".\"workspace_id\", \"resource_policy\".\"organization_id\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.resume_queue": { + "name": "resume_queue", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "paused_execution_id": { + "name": "paused_execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_execution_id": { + "name": "parent_execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "new_execution_id": { + "name": "new_execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "context_id": { + "name": "context_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resume_input": { + "name": "resume_input", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "queued_at": { + "name": "queued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "resume_queue_parent_status_idx": { + "name": "resume_queue_parent_status_idx", + "columns": [ + { + "expression": "parent_execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "resume_queue_new_execution_idx": { + "name": "resume_queue_new_execution_idx", + "columns": [ + { + "expression": "new_execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "resume_queue_paused_execution_id_paused_executions_id_fk": { + "name": "resume_queue_paused_execution_id_paused_executions_id_fk", + "tableFrom": "resume_queue", + "tableTo": "paused_executions", + "columnsFrom": ["paused_execution_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sandbox_image": { + "name": "sandbox_image", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "spec_hash": { + "name": "spec_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "spec": { + "name": "spec", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "sandbox_image_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "image_ref": { + "name": "image_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_image_id": { + "name": "provider_image_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "build_id": { + "name": "build_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "materialization_generation": { + "name": "materialization_generation", + "type": "bigint", + "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 + }, + "error_detail": { + "name": "error_detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "sandbox_image_provider_spec_unique": { + "name": "sandbox_image_provider_spec_unique", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "spec_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sandbox_image_status_idx": { + "name": "sandbox_image_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sandbox_image_last_used_idx": { + "name": "sandbox_image_last_used_idx", + "columns": [ + { + "expression": "last_used_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_connection": { + "name": "scim_connection", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_request_at": { + "name": "last_request_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "reconcile_lock_token": { + "name": "reconcile_lock_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reconcile_lease_at": { + "name": "reconcile_lease_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "reconciled_at": { + "name": "reconciled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "scim_connection_organization_unique": { + "name": "scim_connection_organization_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_connection_reconcile_due_idx": { + "name": "scim_connection_reconcile_due_idx", + "columns": [ + { + "expression": "reconciled_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scim_connection_organization_id_organization_id_fk": { + "name": "scim_connection_organization_id_organization_id_fk", + "tableFrom": "scim_connection", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "scim_connection_created_by_user_id_fk": { + "name": "scim_connection_created_by_user_id_fk", + "tableFrom": "scim_connection", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_credential": { + "name": "scim_credential", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_prefix": { + "name": "token_prefix", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scopes": { + "name": "scopes", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "revoked_by": { + "name": "revoked_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "scim_credential_token_hash_unique": { + "name": "scim_credential_token_hash_unique", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_credential_connection_idx": { + "name": "scim_credential_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scim_credential_connection_id_scim_connection_id_fk": { + "name": "scim_credential_connection_id_scim_connection_id_fk", + "tableFrom": "scim_credential", + "tableTo": "scim_connection", + "columnsFrom": ["connection_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "scim_credential_revoked_by_user_id_fk": { + "name": "scim_credential_revoked_by_user_id_fk", + "tableFrom": "scim_credential", + "tableTo": "user", + "columnsFrom": ["revoked_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "scim_credential_created_by_user_id_fk": { + "name": "scim_credential_created_by_user_id_fk", + "tableFrom": "scim_credential", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_group": { + "name": "scim_group", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name_key": { + "name": "display_name_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "order_key": { + "name": "order_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "scim_group_connection_display_name_unique": { + "name": "scim_group_connection_display_name_unique", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_name_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_group_connection_external_id_unique": { + "name": "scim_group_connection_external_id_unique", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "external_id is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_group_connection_order_idx": { + "name": "scim_group_connection_order_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "order_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scim_group_connection_id_scim_connection_id_fk": { + "name": "scim_group_connection_id_scim_connection_id_fk", + "tableFrom": "scim_group", + "tableTo": "scim_connection", + "columnsFrom": ["connection_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_group_mapping": { + "name": "scim_group_mapping", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_kind": { + "name": "target_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission_group_id": { + "name": "permission_group_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "permission_type": { + "name": "permission_type", + "type": "permission_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "scim_group_mapping_group_idx": { + "name": "scim_group_mapping_group_idx", + "columns": [ + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_group_mapping_permission_group_idx": { + "name": "scim_group_mapping_permission_group_idx", + "columns": [ + { + "expression": "permission_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_group_mapping_workspace_idx": { + "name": "scim_group_mapping_workspace_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_group_mapping_group_target_unique": { + "name": "scim_group_mapping_group_target_unique", + "columns": [ + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"permission_group_id\", \"workspace_id\", \"role\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scim_group_mapping_group_id_scim_group_id_fk": { + "name": "scim_group_mapping_group_id_scim_group_id_fk", + "tableFrom": "scim_group_mapping", + "tableTo": "scim_group", + "columnsFrom": ["group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "scim_group_mapping_permission_group_id_permission_group_id_fk": { + "name": "scim_group_mapping_permission_group_id_permission_group_id_fk", + "tableFrom": "scim_group_mapping", + "tableTo": "permission_group", + "columnsFrom": ["permission_group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "scim_group_mapping_workspace_id_workspace_id_fk": { + "name": "scim_group_mapping_workspace_id_workspace_id_fk", + "tableFrom": "scim_group_mapping", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "scim_group_mapping_created_by_user_id_fk": { + "name": "scim_group_mapping_created_by_user_id_fk", + "tableFrom": "scim_group_mapping", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "scim_group_mapping_target_shape": { + "name": "scim_group_mapping_target_shape", + "value": "(\n (\"scim_group_mapping\".\"target_kind\" = 'permission_group' AND \"scim_group_mapping\".\"permission_group_id\" IS NOT NULL AND \"scim_group_mapping\".\"workspace_id\" IS NULL AND \"scim_group_mapping\".\"permission_type\" IS NULL AND \"scim_group_mapping\".\"role\" IS NULL)\n OR (\"scim_group_mapping\".\"target_kind\" = 'workspace' AND \"scim_group_mapping\".\"workspace_id\" IS NOT NULL AND \"scim_group_mapping\".\"permission_type\" IS NOT NULL AND \"scim_group_mapping\".\"permission_group_id\" IS NULL AND \"scim_group_mapping\".\"role\" IS NULL)\n OR (\"scim_group_mapping\".\"target_kind\" = 'org_role' AND \"scim_group_mapping\".\"role\" IS NOT NULL AND \"scim_group_mapping\".\"permission_group_id\" IS NULL AND \"scim_group_mapping\".\"workspace_id\" IS NULL AND \"scim_group_mapping\".\"permission_type\" IS NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.scim_group_member": { + "name": "scim_group_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scim_user_id": { + "name": "scim_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "scim_group_member_group_user_unique": { + "name": "scim_group_member_group_user_unique", + "columns": [ + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scim_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_group_member_scim_user_idx": { + "name": "scim_group_member_scim_user_idx", + "columns": [ + { + "expression": "scim_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scim_group_member_group_id_scim_group_id_fk": { + "name": "scim_group_member_group_id_scim_group_id_fk", + "tableFrom": "scim_group_member", + "tableTo": "scim_group", + "columnsFrom": ["group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "scim_group_member_scim_user_id_scim_user_id_fk": { + "name": "scim_group_member_scim_user_id_scim_user_id_fk", + "tableFrom": "scim_group_member", + "tableTo": "scim_user", + "columnsFrom": ["scim_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_projection_grant": { + "name": "scim_projection_grant", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scim_user_id": { + "name": "scim_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_kind": { + "name": "target_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission_type": { + "name": "permission_type", + "type": "permission_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "baseline_permission": { + "name": "baseline_permission", + "type": "permission_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "origin": { + "name": "origin", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'directory'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "scim_projection_grant_user_target_unique": { + "name": "scim_projection_grant_user_target_unique", + "columns": [ + { + "expression": "scim_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_projection_grant_connection_idx": { + "name": "scim_projection_grant_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scim_projection_grant_connection_id_scim_connection_id_fk": { + "name": "scim_projection_grant_connection_id_scim_connection_id_fk", + "tableFrom": "scim_projection_grant", + "tableTo": "scim_connection", + "columnsFrom": ["connection_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "scim_projection_grant_scim_user_id_scim_user_id_fk": { + "name": "scim_projection_grant_scim_user_id_scim_user_id_fk", + "tableFrom": "scim_projection_grant", + "tableTo": "scim_user", + "columnsFrom": ["scim_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_request_log": { + "name": "scim_request_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "method": { + "name": "method", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "scim_type": { + "name": "scim_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "detail": { + "name": "detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "scim_request_log_connection_created_idx": { + "name": "scim_request_log_connection_created_idx", + "columns": [ + { + "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": {} + } + }, + "foreignKeys": { + "scim_request_log_connection_id_scim_connection_id_fk": { + "name": "scim_request_log_connection_id_scim_connection_id_fk", + "tableFrom": "scim_request_log", + "tableTo": "scim_connection", + "columnsFrom": ["connection_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_user": { + "name": "scim_user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_name": { + "name": "user_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "attributes": { + "name": "attributes", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "order_key": { + "name": "order_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "scim_user_connection_user_unique": { + "name": "scim_user_connection_user_unique", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_user_connection_user_name_unique": { + "name": "scim_user_connection_user_name_unique", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_user_connection_external_id_unique": { + "name": "scim_user_connection_external_id_unique", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "external_id is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_user_connection_order_idx": { + "name": "scim_user_connection_order_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "order_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_user_user_idx": { + "name": "scim_user_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scim_user_connection_id_scim_connection_id_fk": { + "name": "scim_user_connection_id_scim_connection_id_fk", + "tableFrom": "scim_user", + "tableTo": "scim_connection", + "columnsFrom": ["connection_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "scim_user_user_id_user_id_fk": { + "name": "scim_user_user_id_user_id_fk", + "tableFrom": "scim_user", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_user_tombstone": { + "name": "scim_user_tombstone", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "scim_user_tombstone_connection_external_id_unique": { + "name": "scim_user_tombstone_connection_external_id_unique", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_user_tombstone_user_idx": { + "name": "scim_user_tombstone_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scim_user_tombstone_connection_id_scim_connection_id_fk": { + "name": "scim_user_tombstone_connection_id_scim_connection_id_fk", + "tableFrom": "scim_user_tombstone", + "tableTo": "scim_connection", + "columnsFrom": ["connection_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "scim_user_tombstone_user_id_user_id_fk": { + "name": "scim_user_tombstone_user_id_user_id_fk", + "tableFrom": "scim_user_tombstone", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.secret_usage": { + "name": "secret_usage", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret_name": { + "name": "secret_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret_scope": { + "name": "secret_scope", + "type": "secret_usage_scope", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "secret_owner_user_id": { + "name": "secret_owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "source": { + "name": "source", + "type": "secret_usage_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "usage_date": { + "name": "usage_date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "use_count": { + "name": "use_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "last_execution_id": { + "name": "last_execution_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_trigger": { + "name": "last_trigger", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "secret_usage_bucket_unique": { + "name": "secret_usage_bucket_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "secret_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "secret_scope", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "secret_owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "actor_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "usage_date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secret_usage_secret_recent_idx": { + "name": "secret_usage_secret_recent_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "secret_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "secret_scope", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "secret_owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_used_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "secret_usage_workspace_id_workspace_id_fk": { + "name": "secret_usage_workspace_id_workspace_id_fk", + "tableFrom": "secret_usage", + "tableTo": "workspace", + "columnsFrom": ["workspace_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", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "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 + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "session_user_id_idx": { + "name": "session_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "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" + }, + "session_active_organization_id_organization_id_fk": { + "name": "session_active_organization_id_organization_id_fk", + "tableFrom": "session", + "tableTo": "organization", + "columnsFrom": ["active_organization_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.settings": { + "name": "settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "theme": { + "name": "theme", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "auto_connect": { + "name": "auto_connect", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "telemetry_enabled": { + "name": "telemetry_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "email_preferences": { + "name": "email_preferences", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "billing_usage_notifications_enabled": { + "name": "billing_usage_notifications_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "show_training_controls": { + "name": "show_training_controls", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "super_user_mode_enabled": { + "name": "super_user_mode_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "mothership_environment": { + "name": "mothership_environment", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "error_notifications_enabled": { + "name": "error_notifications_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "snap_to_grid_size": { + "name": "snap_to_grid_size", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "show_action_bar": { + "name": "show_action_bar", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "auto_focus_on_click": { + "name": "auto_focus_on_click", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "copilot_enabled_models": { + "name": "copilot_enabled_models", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "copilot_auto_allowed_tools": { + "name": "copilot_auto_allowed_tools", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "last_active_workspace_id": { + "name": "last_active_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "settings_user_id_user_id_fk": { + "name": "settings_user_id_user_id_fk", + "tableFrom": "settings", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "settings_user_id_unique": { + "name": "settings_user_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sim_trigger_state": { + "name": "sim_trigger_state", + "schema": "", + "columns": { + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "block_id": { + "name": "block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_key": { + "name": "scope_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "last_fired_at": { + "name": "last_fired_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "sim_trigger_state_workflow_id_workflow_id_fk": { + "name": "sim_trigger_state_workflow_id_workflow_id_fk", + "tableFrom": "sim_trigger_state", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "sim_trigger_state_workflow_id_block_id_scope_key_pk": { + "name": "sim_trigger_state_workflow_id_block_id_scope_key_pk", + "columns": ["workflow_id", "block_id", "scope_key"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.skill": { + "name": "skill", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "skill_workspace_name_unique": { + "name": "skill_workspace_name_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "skill_workspace_id_workspace_id_fk": { + "name": "skill_workspace_id_workspace_id_fk", + "tableFrom": "skill", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "skill_user_id_user_id_fk": { + "name": "skill_user_id_user_id_fk", + "tableFrom": "skill", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.skill_member": { + "name": "skill_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "skill_id": { + "name": "skill_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "invited_by": { + "name": "invited_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "skill_member_user_id_idx": { + "name": "skill_member_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "skill_member_unique": { + "name": "skill_member_unique", + "columns": [ + { + "expression": "skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "skill_member_skill_id_skill_id_fk": { + "name": "skill_member_skill_id_skill_id_fk", + "tableFrom": "skill_member", + "tableTo": "skill", + "columnsFrom": ["skill_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "skill_member_user_id_user_id_fk": { + "name": "skill_member_user_id_user_id_fk", + "tableFrom": "skill_member", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "skill_member_invited_by_user_id_fk": { + "name": "skill_member_invited_by_user_id_fk", + "tableFrom": "skill_member", + "tableTo": "user", + "columnsFrom": ["invited_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.slack_app": { + "name": "slack_app", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_client_secret": { + "name": "encrypted_client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_signing_secret": { + "name": "encrypted_signing_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revision": { + "name": "revision", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "slack_app_organization_id_organization_id_fk": { + "name": "slack_app_organization_id_organization_id_fk", + "tableFrom": "slack_app", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "slack_app_owner_check": { + "name": "slack_app_owner_check", + "value": "(\"slack_app\".\"kind\" = 'custom' AND \"slack_app\".\"organization_id\" IS NOT NULL) OR (\"slack_app\".\"kind\" = 'shared' AND \"slack_app\".\"organization_id\" IS NULL)" + }, + "slack_app_custom_credentials_check": { + "name": "slack_app_custom_credentials_check", + "value": "\"slack_app\".\"kind\" = 'shared' OR (\"slack_app\".\"client_id\" IS NOT NULL AND \"slack_app\".\"encrypted_client_secret\" IS NOT NULL AND \"slack_app\".\"encrypted_signing_secret\" IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.slack_search_installation": { + "name": "slack_search_installation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "app_id": { + "name": "app_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slack_app_id": { + "name": "slack_app_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "team_name": { + "name": "team_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bot_user_id": { + "name": "bot_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enterprise_id": { + "name": "enterprise_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "credential_version": { + "name": "credential_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_outcome": { + "name": "last_outcome", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_event_at": { + "name": "last_event_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "slack_search_installation_organization_idx": { + "name": "slack_search_installation_organization_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "slack_search_installation_credential_unique": { + "name": "slack_search_installation_credential_unique", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "slack_search_installation_app_team_unique": { + "name": "slack_search_installation_app_team_unique", + "columns": [ + { + "expression": "app_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "slack_search_installation_active_team_unique": { + "name": "slack_search_installation_active_team_unique", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"slack_search_installation\".\"enabled\" = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "slack_search_installation_organization_id_organization_id_fk": { + "name": "slack_search_installation_organization_id_organization_id_fk", + "tableFrom": "slack_search_installation", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "slack_search_installation_credential_id_credential_id_fk": { + "name": "slack_search_installation_credential_id_credential_id_fk", + "tableFrom": "slack_search_installation", + "tableTo": "credential", + "columnsFrom": ["credential_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "slack_search_installation_slack_app_id_slack_app_id_fk": { + "name": "slack_search_installation_slack_app_id_slack_app_id_fk", + "tableFrom": "slack_search_installation", + "tableTo": "slack_app", + "columnsFrom": ["slack_app_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.slack_search_turn": { + "name": "slack_search_turn", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "ordinal": { + "name": "ordinal", + "type": "integer", + "primaryKey": false, + "notNull": true, + "identity": { + "type": "always", + "name": "slack_search_turn_ordinal_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "installation_id": { + "name": "installation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "conversation_key": { + "name": "conversation_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "lease_id": { + "name": "lease_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lease_expires_at": { + "name": "lease_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "slack_search_turn_event_unique": { + "name": "slack_search_turn_event_unique", + "columns": [ + { + "expression": "installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "slack_search_turn_pending_idx": { + "name": "slack_search_turn_pending_idx", + "columns": [ + { + "expression": "installation_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": {} + }, + "slack_search_turn_thread_idx": { + "name": "slack_search_turn_thread_idx", + "columns": [ + { + "expression": "conversation_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "slack_search_turn_active_thread_unique": { + "name": "slack_search_turn_active_thread_unique", + "columns": [ + { + "expression": "conversation_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"slack_search_turn\".\"status\" = 'running'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "slack_search_turn_installation_id_slack_search_installation_id_fk": { + "name": "slack_search_turn_installation_id_slack_search_installation_id_fk", + "tableFrom": "slack_search_turn", + "tableTo": "slack_search_installation", + "columnsFrom": ["installation_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sso_domain": { + "name": "sso_domain", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "verification_token": { + "name": "verification_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "verified_at": { + "name": "verified_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "primary_provider_id": { + "name": "primary_provider_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "sso_domain_organization_id_idx": { + "name": "sso_domain_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sso_domain_domain_idx": { + "name": "sso_domain_domain_idx", + "columns": [ + { + "expression": "domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sso_domain_org_domain_unique": { + "name": "sso_domain_org_domain_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sso_domain_verified_unique": { + "name": "sso_domain_verified_unique", + "columns": [ + { + "expression": "domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "status = 'verified'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sso_domain_organization_id_organization_id_fk": { + "name": "sso_domain_organization_id_organization_id_fk", + "tableFrom": "sso_domain", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "sso_domain_created_by_user_id_fk": { + "name": "sso_domain_created_by_user_id_fk", + "tableFrom": "sso_domain", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sso_provider": { + "name": "sso_provider", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "issuer": { + "name": "issuer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oidc_config": { + "name": "oidc_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "saml_config": { + "name": "saml_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "domain_verified": { + "name": "domain_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "jit_provisioning_enabled": { + "name": "jit_provisioning_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + } + }, + "indexes": { + "sso_provider_provider_id_unique": { + "name": "sso_provider_provider_id_unique", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sso_provider_domain_idx": { + "name": "sso_provider_domain_idx", + "columns": [ + { + "expression": "domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sso_provider_user_id_idx": { + "name": "sso_provider_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sso_provider_organization_id_idx": { + "name": "sso_provider_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sso_provider_user_id_user_id_fk": { + "name": "sso_provider_user_id_user_id_fk", + "tableFrom": "sso_provider", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "sso_provider_organization_id_organization_id_fk": { + "name": "sso_provider_organization_id_organization_id_fk", + "tableFrom": "sso_provider", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.subscription": { + "name": "subscription", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "plan": { + "name": "plan", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_subscription_id": { + "name": "stripe_subscription_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "period_start": { + "name": "period_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "period_end": { + "name": "period_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "cancel_at_period_end": { + "name": "cancel_at_period_end", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "cancel_at": { + "name": "cancel_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "canceled_at": { + "name": "canceled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "seats": { + "name": "seats", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "trial_start": { + "name": "trial_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "trial_end": { + "name": "trial_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "billing_interval": { + "name": "billing_interval", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_schedule_id": { + "name": "stripe_schedule_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "last_closed_period_start": { + "name": "last_closed_period_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "subscription_reference_status_idx": { + "name": "subscription_reference_status_idx", + "columns": [ + { + "expression": "reference_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "subscription_cycle_close_lagging_idx": { + "name": "subscription_cycle_close_lagging_idx", + "columns": [ + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"subscription\".\"status\" in ('active', 'past_due') and \"subscription\".\"period_start\" is not null and (\"subscription\".\"last_closed_period_start\" is null or \"subscription\".\"last_closed_period_start\" < \"subscription\".\"period_start\")", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "check_enterprise_metadata": { + "name": "check_enterprise_metadata", + "value": "plan != 'enterprise' OR metadata IS NOT NULL" + } + }, + "isRLSEnabled": false + }, + "public.table_jobs": { + "name": "table_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "rows_processed": { + "name": "rows_processed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "table_jobs_one_active_per_table": { + "name": "table_jobs_one_active_per_table", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"table_jobs\".\"status\" = 'running' AND \"table_jobs\".\"type\" <> 'export'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_jobs_watchdog_idx": { + "name": "table_jobs_watchdog_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_jobs_table_started_idx": { + "name": "table_jobs_table_started_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "table_jobs_table_id_user_table_definitions_id_fk": { + "name": "table_jobs_table_id_user_table_definitions_id_fk", + "tableFrom": "table_jobs", + "tableTo": "user_table_definitions", + "columnsFrom": ["table_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "table_jobs_workspace_id_workspace_id_fk": { + "name": "table_jobs_workspace_id_workspace_id_fk", + "tableFrom": "table_jobs", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.table_row_executions": { + "name": "table_row_executions", + "schema": "", + "columns": { + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "row_id": { + "name": "row_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "job_id": { + "name": "job_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "running_block_ids": { + "name": "running_block_ids", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "block_errors": { + "name": "block_errors", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "capability_governed_user_id": { + "name": "capability_governed_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enrichment_details": { + "name": "enrichment_details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "table_row_executions_table_status_idx": { + "name": "table_row_executions_table_status_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"table_row_executions\".\"status\" IN ('queued', 'running', 'pending')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_row_executions_execution_id_idx": { + "name": "table_row_executions_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"table_row_executions\".\"execution_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_row_executions_table_group_idx": { + "name": "table_row_executions_table_group_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "table_row_executions_table_id_user_table_definitions_id_fk": { + "name": "table_row_executions_table_id_user_table_definitions_id_fk", + "tableFrom": "table_row_executions", + "tableTo": "user_table_definitions", + "columnsFrom": ["table_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "table_row_executions_row_id_user_table_rows_id_fk": { + "name": "table_row_executions_row_id_user_table_rows_id_fk", + "tableFrom": "table_row_executions", + "tableTo": "user_table_rows", + "columnsFrom": ["row_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "table_row_executions_capability_governed_user_id_user_id_fk": { + "name": "table_row_executions_capability_governed_user_id_user_id_fk", + "tableFrom": "table_row_executions", + "tableTo": "user", + "columnsFrom": ["capability_governed_user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "table_row_executions_row_id_group_id_pk": { + "name": "table_row_executions_row_id_group_id_pk", + "columns": ["row_id", "group_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.table_run_dispatches": { + "name": "table_run_dispatches", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "cursor": { + "name": "cursor", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "limit": { + "name": "limit", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "processed_count": { + "name": "processed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_manual_run": { + "name": "is_manual_run", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "triggered_by_user_id": { + "name": "triggered_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "capability_governed_user_id": { + "name": "capability_governed_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_at": { + "name": "requested_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "heartbeat_at": { + "name": "heartbeat_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "table_run_dispatches_active_idx": { + "name": "table_run_dispatches_active_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_run_dispatches_watchdog_idx": { + "name": "table_run_dispatches_watchdog_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_run_dispatches_governed_active_idx": { + "name": "table_run_dispatches_governed_active_idx", + "columns": [ + { + "expression": "capability_governed_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"table_run_dispatches\".\"status\" IN ('pending', 'dispatching')", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "table_run_dispatches_table_id_user_table_definitions_id_fk": { + "name": "table_run_dispatches_table_id_user_table_definitions_id_fk", + "tableFrom": "table_run_dispatches", + "tableTo": "user_table_definitions", + "columnsFrom": ["table_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "table_run_dispatches_workspace_id_workspace_id_fk": { + "name": "table_run_dispatches_workspace_id_workspace_id_fk", + "tableFrom": "table_run_dispatches", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "table_run_dispatches_triggered_by_user_id_user_id_fk": { + "name": "table_run_dispatches_triggered_by_user_id_user_id_fk", + "tableFrom": "table_run_dispatches", + "tableTo": "user", + "columnsFrom": ["triggered_by_user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "table_run_dispatches_capability_governed_user_id_user_id_fk": { + "name": "table_run_dispatches_capability_governed_user_id_user_id_fk", + "tableFrom": "table_run_dispatches", + "tableTo": "user", + "columnsFrom": ["capability_governed_user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.table_views": { + "name": "table_views", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "table_views_table_created_idx": { + "name": "table_views_table_created_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_views_workspace_created_idx": { + "name": "table_views_workspace_created_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_views_table_default_unique": { + "name": "table_views_table_default_unique", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "is_default = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "table_views_table_id_user_table_definitions_id_fk": { + "name": "table_views_table_id_user_table_definitions_id_fk", + "tableFrom": "table_views", + "tableTo": "user_table_definitions", + "columnsFrom": ["table_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "table_views_workspace_id_workspace_id_fk": { + "name": "table_views_workspace_id_workspace_id_fk", + "tableFrom": "table_views", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "table_views_created_by_user_id_fk": { + "name": "table_views_created_by_user_id_fk", + "tableFrom": "table_views", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.upload_session": { + "name": "upload_session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "purpose": { + "name": "purpose", + "type": "upload_session_purpose", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "method": { + "name": "method", + "type": "upload_session_method", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "storage_context": { + "name": "storage_context", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "final_key": { + "name": "final_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "storage_provider": { + "name": "storage_provider", + "type": "upload_session_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "provider_upload_id": { + "name": "provider_upload_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_object_version": { + "name": "provider_object_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "file_name": { + "name": "file_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_size": { + "name": "file_size", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "part_size": { + "name": "part_size", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "part_count": { + "name": "part_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "upload_session_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'uploading'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "processing_lease_id": { + "name": "processing_lease_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "processing_lease_expires_at": { + "name": "processing_lease_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_file_id": { + "name": "completed_file_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "upload_session_token_hash_unique": { + "name": "upload_session_token_hash_unique", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "upload_session_final_key_unique": { + "name": "upload_session_final_key_unique", + "columns": [ + { + "expression": "final_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "upload_session_status_expires_at_idx": { + "name": "upload_session_status_expires_at_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": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.usage_log": { + "name": "usage_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "category": { + "name": "category", + "type": "usage_log_category", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "usage_log_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "cost": { + "name": "cost", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "event_key": { + "name": "event_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "billing_entity_type": { + "name": "billing_entity_type", + "type": "billing_entity_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "billing_entity_id": { + "name": "billing_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "billing_period_start": { + "name": "billing_period_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "billing_period_end": { + "name": "billing_period_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "usage_log_user_created_at_idx": { + "name": "usage_log_user_created_at_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_source_idx": { + "name": "usage_log_source_idx", + "columns": [ + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_workspace_id_idx": { + "name": "usage_log_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_workflow_id_idx": { + "name": "usage_log_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_event_key_unique": { + "name": "usage_log_event_key_unique", + "columns": [ + { + "expression": "event_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"usage_log\".\"event_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_billing_entity_period_idx": { + "name": "usage_log_billing_entity_period_idx", + "columns": [ + { + "expression": "billing_entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_start", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_end", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_log\".\"billing_entity_type\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_billing_period_cost_idx": { + "name": "usage_log_billing_period_cost_idx", + "columns": [ + { + "expression": "billing_entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_start", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_end", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_log\".\"billing_entity_type\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_billing_entity_created_at_cost_idx": { + "name": "usage_log_billing_entity_created_at_cost_idx", + "columns": [ + { + "expression": "billing_entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_log\".\"billing_entity_type\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_workspace_created_at_idx": { + "name": "usage_log_workspace_created_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_execution_id_idx": { + "name": "usage_log_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "usage_log_user_id_user_id_fk": { + "name": "usage_log_user_id_user_id_fk", + "tableFrom": "usage_log", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "usage_log_workspace_id_workspace_id_fk": { + "name": "usage_log_workspace_id_workspace_id_fk", + "tableFrom": "usage_log", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "usage_log_workflow_id_workflow_id_fk": { + "name": "usage_log_workflow_id_workflow_id_fk", + "tableFrom": "usage_log", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "usage_log_billing_scope_all_or_none": { + "name": "usage_log_billing_scope_all_or_none", + "value": "(\n (\"usage_log\".\"billing_entity_type\" IS NULL AND \"usage_log\".\"billing_entity_id\" IS NULL AND \"usage_log\".\"billing_period_start\" IS NULL AND \"usage_log\".\"billing_period_end\" IS NULL)\n OR\n (\"usage_log\".\"billing_entity_type\" IS NOT NULL AND \"usage_log\".\"billing_entity_id\" IS NOT NULL AND \"usage_log\".\"billing_period_start\" IS NOT NULL AND \"usage_log\".\"billing_period_end\" IS NOT NULL AND \"usage_log\".\"billing_period_start\" < \"usage_log\".\"billing_period_end\")\n )" + } + }, + "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 + }, + "normalized_email": { + "name": "normalized_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "suspended_at": { + "name": "suspended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "suspension_source": { + "name": "suspension_source", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_email_lower_idx": { + "name": "user_email_lower_idx", + "columns": [ + { + "expression": "lower(btrim(\"email\"))", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + }, + "user_normalized_email_unique": { + "name": "user_normalized_email_unique", + "nullsNotDistinct": false, + "columns": ["normalized_email"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_stats": { + "name": "user_stats", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "current_usage_limit": { + "name": "current_usage_limit", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'5'" + }, + "usage_limit_updated_at": { + "name": "usage_limit_updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "last_period_cost": { + "name": "last_period_cost", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "billed_overage_this_period": { + "name": "billed_overage_this_period", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "credit_balance": { + "name": "credit_balance", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "last_period_copilot_cost": { + "name": "last_period_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "storage_used_bytes": { + "name": "storage_used_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "billing_blocked": { + "name": "billing_blocked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "billing_blocked_reason": { + "name": "billing_blocked_reason", + "type": "billing_blocked_reason", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "limit_notifications": { + "name": "limit_notifications", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + } + }, + "indexes": {}, + "foreignKeys": { + "user_stats_user_id_user_id_fk": { + "name": "user_stats_user_id_user_id_fk", + "tableFrom": "user_stats", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_stats_user_id_unique": { + "name": "user_stats_user_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_table_definitions": { + "name": "user_table_definitions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "folder_id": { + "name": "folder_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "schema": { + "name": "schema", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "max_rows": { + "name": "max_rows", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10000 + }, + "row_count": { + "name": "row_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "rows_version": { + "name": "rows_version", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "schema_locked": { + "name": "schema_locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "insert_locked": { + "name": "insert_locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "update_locked": { + "name": "update_locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "delete_locked": { + "name": "delete_locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_table_def_workspace_id_idx": { + "name": "user_table_def_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_def_folder_id_idx": { + "name": "user_table_def_folder_id_idx", + "columns": [ + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_def_workspace_name_unique": { + "name": "user_table_def_workspace_name_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"user_table_definitions\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_def_archived_at_idx": { + "name": "user_table_def_archived_at_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_def_workspace_archived_partial_idx": { + "name": "user_table_def_workspace_archived_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"user_table_definitions\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_table_definitions_workspace_id_workspace_id_fk": { + "name": "user_table_definitions_workspace_id_workspace_id_fk", + "tableFrom": "user_table_definitions", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_table_definitions_folder_id_folder_id_fk": { + "name": "user_table_definitions_folder_id_folder_id_fk", + "tableFrom": "user_table_definitions", + "tableTo": "folder", + "columnsFrom": ["folder_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "user_table_definitions_created_by_user_id_fk": { + "name": "user_table_definitions_created_by_user_id_fk", + "tableFrom": "user_table_definitions", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_table_row_secret_provenance": { + "name": "user_table_row_secret_provenance", + "schema": "", + "columns": { + "row_id": { + "name": "row_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "content_updated_at": { + "name": "content_updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entries": { + "name": "entries", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_table_row_secret_provenance_row_id_user_table_rows_id_fk": { + "name": "user_table_row_secret_provenance_row_id_user_table_rows_id_fk", + "tableFrom": "user_table_row_secret_provenance", + "tableTo": "user_table_rows", + "columnsFrom": ["row_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "user_table_row_secret_provenance_status_check": { + "name": "user_table_row_secret_provenance_status_check", + "value": "\"user_table_row_secret_provenance\".\"status\" IN ('exact', 'unknown')" + } + }, + "isRLSEnabled": false + }, + "public.user_table_rows": { + "name": "user_table_rows", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "order_key": { + "name": "order_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_provenance_version": { + "name": "secret_provenance_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_table_rows_tenant_data_gin_idx": { + "name": "user_table_rows_tenant_data_gin_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"data\" jsonb_path_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "user_table_rows_workspace_table_idx": { + "name": "user_table_rows_workspace_table_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_rows_table_position_idx": { + "name": "user_table_rows_table_position_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_rows_table_order_key_idx": { + "name": "user_table_rows_table_order_key_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "order_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_rows_table_created_id_idx": { + "name": "user_table_rows_table_created_id_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_rows_table_id_id_idx": { + "name": "user_table_rows_table_id_id_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_table_rows_table_id_user_table_definitions_id_fk": { + "name": "user_table_rows_table_id_user_table_definitions_id_fk", + "tableFrom": "user_table_rows", + "tableTo": "user_table_definitions", + "columnsFrom": ["table_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_table_rows_workspace_id_workspace_id_fk": { + "name": "user_table_rows_workspace_id_workspace_id_fk", + "tableFrom": "user_table_rows", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_table_rows_created_by_user_id_fk": { + "name": "user_table_rows_created_by_user_id_fk", + "tableFrom": "user_table_rows", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "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", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "verification_identifier_idx": { + "name": "verification_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "verification_expires_at_idx": { + "name": "verification_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.waitlist": { + "name": "waitlist", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "waitlist_email_unique": { + "name": "waitlist_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.webhook": { + "name": "webhook", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deployment_version_id": { + "name": "deployment_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "registration_status": { + "name": "registration_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "registration_generation": { + "name": "registration_generation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "config_fingerprint": { + "name": "config_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prepared_at": { + "name": "prepared_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "block_id": { + "name": "block_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "routing_key": { + "name": "routing_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_config": { + "name": "provider_config", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "failed_count": { + "name": "failed_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "last_failed_at": { + "name": "last_failed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "path_deployment_unique": { + "name": "path_deployment_unique", + "columns": [ + { + "expression": "path", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"webhook\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_workflow_deployment_idx": { + "name": "webhook_workflow_deployment_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_routing_key_active_idx": { + "name": "webhook_routing_key_active_idx", + "columns": [ + { + "expression": "routing_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"webhook\".\"archived_at\" IS NULL AND \"webhook\".\"routing_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_archived_at_partial_idx": { + "name": "webhook_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"webhook\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_webhook_on_provider_is_active_workflow_id_deploym_bdeed5468": { + "name": "idx_webhook_on_provider_is_active_workflow_id_deploym_bdeed5468", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_webhook_on_workflow_id_block_id_updated_at_desc": { + "name": "idx_webhook_on_workflow_id_block_id_updated_at_desc", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_active_registration_unique": { + "name": "webhook_active_registration_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"webhook\".\"registration_status\" = 'active' AND \"webhook\".\"block_id\" IS NOT NULL AND \"webhook\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_candidate_registration_unique": { + "name": "webhook_candidate_registration_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"webhook\".\"registration_status\" = 'candidate' AND \"webhook\".\"block_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_registration_status_generation_idx": { + "name": "webhook_registration_status_generation_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "registration_status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "registration_generation", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "webhook_workflow_id_workflow_id_fk": { + "name": "webhook_workflow_id_workflow_id_fk", + "tableFrom": "webhook", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "webhook_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "webhook_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "webhook", + "tableTo": "workflow_deployment_version", + "columnsFrom": ["deployment_version_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "webhook_registration_status_check": { + "name": "webhook_registration_status_check", + "value": "\"webhook\".\"registration_status\" IS NULL OR \"webhook\".\"registration_status\" IN ('active', 'candidate', 'retired', 'orphaned')" + }, + "webhook_registration_generation_check": { + "name": "webhook_registration_generation_check", + "value": "\"webhook\".\"registration_generation\" IS NULL OR \"webhook\".\"registration_generation\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.webhook_path_claim": { + "name": "webhook_path_claim", + "schema": "", + "columns": { + "path": { + "name": "path", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "webhook_path_claim_workflow_idx": { + "name": "webhook_path_claim_workflow_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "webhook_path_claim_workflow_id_workflow_id_fk": { + "name": "webhook_path_claim_workflow_id_workflow_id_fk", + "tableFrom": "webhook_path_claim", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "webhook_path_claim_generation_check": { + "name": "webhook_path_claim_generation_check", + "value": "\"webhook_path_claim\".\"generation\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.workflow": { + "name": "workflow", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "folder_id": { + "name": "folder_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_synced": { + "name": "last_synced", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "is_deployed": { + "name": "is_deployed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "deployed_at": { + "name": "deployed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "is_public_api": { + "name": "is_public_api", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "locked": { + "name": "locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "fork_sync_excluded": { + "name": "fork_sync_excluded", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "run_count": { + "name": "run_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "variables": { + "name": "variables", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workflow_user_id_idx": { + "name": "workflow_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_workspace_id_idx": { + "name": "workflow_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_user_workspace_idx": { + "name": "workflow_user_workspace_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_workspace_folder_name_active_unique": { + "name": "workflow_workspace_folder_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"folder_id\", '')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_folder_sort_idx": { + "name": "workflow_folder_sort_idx", + "columns": [ + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_active_workspace_sort_idx": { + "name": "workflow_active_workspace_sort_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_archived_at_idx": { + "name": "workflow_archived_at_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_workspace_archived_partial_idx": { + "name": "workflow_workspace_archived_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_user_id_user_id_fk": { + "name": "workflow_user_id_user_id_fk", + "tableFrom": "workflow", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_workspace_id_workspace_id_fk": { + "name": "workflow_workspace_id_workspace_id_fk", + "tableFrom": "workflow", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_folder_id_folder_id_fk": { + "name": "workflow_folder_id_folder_id_fk", + "tableFrom": "workflow", + "tableTo": "folder", + "columnsFrom": ["folder_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_blocks": { + "name": "workflow_blocks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "position_x": { + "name": "position_x", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "position_y": { + "name": "position_y", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "horizontal_handles": { + "name": "horizontal_handles", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_wide": { + "name": "is_wide", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "advanced_mode": { + "name": "advanced_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "trigger_mode": { + "name": "trigger_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "error_enabled": { + "name": "error_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "retry": { + "name": "retry", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "locked": { + "name": "locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "height": { + "name": "height", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "sub_blocks": { + "name": "sub_blocks", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "outputs": { + "name": "outputs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_blocks_workflow_id_idx": { + "name": "workflow_blocks_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_blocks_type_idx": { + "name": "workflow_blocks_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_blocks_workflow_id_workflow_id_fk": { + "name": "workflow_blocks_workflow_id_workflow_id_fk", + "tableFrom": "workflow_blocks", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_checkpoints": { + "name": "workflow_checkpoints", + "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 + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_state": { + "name": "workflow_state", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_checkpoints_user_id_idx": { + "name": "workflow_checkpoints_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_workflow_id_idx": { + "name": "workflow_checkpoints_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_chat_id_idx": { + "name": "workflow_checkpoints_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_message_id_idx": { + "name": "workflow_checkpoints_message_id_idx", + "columns": [ + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_user_workflow_idx": { + "name": "workflow_checkpoints_user_workflow_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_workflow_chat_idx": { + "name": "workflow_checkpoints_workflow_chat_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_created_at_idx": { + "name": "workflow_checkpoints_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_chat_created_at_idx": { + "name": "workflow_checkpoints_chat_created_at_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_checkpoints_user_id_user_id_fk": { + "name": "workflow_checkpoints_user_id_user_id_fk", + "tableFrom": "workflow_checkpoints", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_checkpoints_workflow_id_workflow_id_fk": { + "name": "workflow_checkpoints_workflow_id_workflow_id_fk", + "tableFrom": "workflow_checkpoints", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_checkpoints_chat_id_copilot_chats_id_fk": { + "name": "workflow_checkpoints_chat_id_copilot_chats_id_fk", + "tableFrom": "workflow_checkpoints", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_deployment_operation": { + "name": "workflow_deployment_operation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deployment_version_id": { + "name": "deployment_version_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "previous_active_version_id": { + "name": "previous_active_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "protocol_version": { + "name": "protocol_version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'preparing'" + }, + "component_readiness": { + "name": "component_readiness", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_hash": { + "name": "request_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_deployment_operation_workflow_generation_unique": { + "name": "workflow_deployment_operation_workflow_generation_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "generation", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_deployment_operation_workflow_idempotency_unique": { + "name": "workflow_deployment_operation_workflow_idempotency_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow_deployment_operation\".\"idempotency_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_deployment_operation_workflow_in_flight_unique": { + "name": "workflow_deployment_operation_workflow_in_flight_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow_deployment_operation\".\"status\" IN ('preparing', 'activating')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_deployment_operation_workflow_status_idx": { + "name": "workflow_deployment_operation_workflow_status_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_deployment_operation_deployment_version_idx": { + "name": "workflow_deployment_operation_deployment_version_idx", + "columns": [ + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_deployment_operation_workflow_version_generation_idx": { + "name": "workflow_deployment_operation_workflow_version_generation_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "generation", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_deployment_operation_workflow_id_workflow_id_fk": { + "name": "workflow_deployment_operation_workflow_id_workflow_id_fk", + "tableFrom": "workflow_deployment_operation", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_deployment_operation_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "workflow_deployment_operation_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "workflow_deployment_operation", + "tableTo": "workflow_deployment_version", + "columnsFrom": ["deployment_version_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_deployment_operation_previous_active_version_id_workflow_deployment_version_id_fk": { + "name": "workflow_deployment_operation_previous_active_version_id_workflow_deployment_version_id_fk", + "tableFrom": "workflow_deployment_operation", + "tableTo": "workflow_deployment_version", + "columnsFrom": ["previous_active_version_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "workflow_deployment_operation_action_check": { + "name": "workflow_deployment_operation_action_check", + "value": "\"workflow_deployment_operation\".\"action\" IN ('deploy', 'activate')" + }, + "workflow_deployment_operation_status_check": { + "name": "workflow_deployment_operation_status_check", + "value": "\"workflow_deployment_operation\".\"status\" IN ('preparing', 'activating', 'active', 'failed', 'superseded')" + }, + "workflow_deployment_operation_generation_check": { + "name": "workflow_deployment_operation_generation_check", + "value": "\"workflow_deployment_operation\".\"generation\" > 0" + }, + "workflow_deployment_operation_protocol_version_check": { + "name": "workflow_deployment_operation_protocol_version_check", + "value": "\"workflow_deployment_operation\".\"protocol_version\" > 0" + } + }, + "isRLSEnabled": false + }, + "public.workflow_deployment_version": { + "name": "workflow_deployment_version", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workflow_deployment_version_workflow_version_unique": { + "name": "workflow_deployment_version_workflow_version_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_deployment_version_workflow_active_idx": { + "name": "workflow_deployment_version_workflow_active_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_deployment_version_created_at_idx": { + "name": "workflow_deployment_version_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_deployment_version_workflow_id_workflow_id_fk": { + "name": "workflow_deployment_version_workflow_id_workflow_id_fk", + "tableFrom": "workflow_deployment_version", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_edges": { + "name": "workflow_edges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_block_id": { + "name": "source_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_block_id": { + "name": "target_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_handle": { + "name": "source_handle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_handle": { + "name": "target_handle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_edges_workflow_id_idx": { + "name": "workflow_edges_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_edges_workflow_source_idx": { + "name": "workflow_edges_workflow_source_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_edges_workflow_target_idx": { + "name": "workflow_edges_workflow_target_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_edges_workflow_id_workflow_id_fk": { + "name": "workflow_edges_workflow_id_workflow_id_fk", + "tableFrom": "workflow_edges", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_edges_source_block_id_workflow_blocks_id_fk": { + "name": "workflow_edges_source_block_id_workflow_blocks_id_fk", + "tableFrom": "workflow_edges", + "tableTo": "workflow_blocks", + "columnsFrom": ["source_block_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_edges_target_block_id_workflow_blocks_id_fk": { + "name": "workflow_edges_target_block_id_workflow_blocks_id_fk", + "tableFrom": "workflow_edges", + "tableTo": "workflow_blocks", + "columnsFrom": ["target_block_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_execution_logs": { + "name": "workflow_execution_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state_snapshot_id": { + "name": "state_snapshot_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deployment_version_id": { + "name": "deployment_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "execution_deadline_at": { + "name": "execution_deadline_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "total_duration_ms": { + "name": "total_duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "execution_data": { + "name": "execution_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "cost_total": { + "name": "cost_total", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "models_used": { + "name": "models_used", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "files": { + "name": "files", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_execution_logs_workflow_id_idx": { + "name": "workflow_execution_logs_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_state_snapshot_id_idx": { + "name": "workflow_execution_logs_state_snapshot_id_idx", + "columns": [ + { + "expression": "state_snapshot_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_deployment_version_id_idx": { + "name": "workflow_execution_logs_deployment_version_id_idx", + "columns": [ + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_trigger_idx": { + "name": "workflow_execution_logs_trigger_idx", + "columns": [ + { + "expression": "trigger", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_level_idx": { + "name": "workflow_execution_logs_level_idx", + "columns": [ + { + "expression": "level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_started_at_idx": { + "name": "workflow_execution_logs_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_execution_id_unique": { + "name": "workflow_execution_logs_execution_id_unique", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_workflow_started_at_idx": { + "name": "workflow_execution_logs_workflow_started_at_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_workspace_started_at_idx": { + "name": "workflow_execution_logs_workspace_started_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_workspace_activity_idx": { + "name": "workflow_execution_logs_workspace_activity_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "total_duration_ms", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "trigger", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": true, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_workspace_started_at_id_desc_idx": { + "name": "workflow_execution_logs_workspace_started_at_id_desc_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"started_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"id\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_workspace_cost_total_idx": { + "name": "workflow_execution_logs_workspace_cost_total_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_total", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_models_used_idx": { + "name": "workflow_execution_logs_models_used_idx", + "columns": [ + { + "expression": "models_used", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "workflow_execution_logs_workspace_ended_at_id_idx": { + "name": "workflow_execution_logs_workspace_ended_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"ended_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_running_started_at_idx": { + "name": "workflow_execution_logs_running_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "status = 'running'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_running_deadline_idx": { + "name": "workflow_execution_logs_running_deadline_idx", + "columns": [ + { + "expression": "execution_deadline_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_execution_logs\".\"status\" = 'running' AND \"workflow_execution_logs\".\"execution_deadline_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_redacting_started_at_idx": { + "name": "workflow_execution_logs_redacting_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "status = 'redacting'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_redacting_deadline_idx": { + "name": "workflow_execution_logs_redacting_deadline_idx", + "columns": [ + { + "expression": "execution_deadline_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_execution_logs\".\"status\" = 'redacting' AND \"workflow_execution_logs\".\"execution_deadline_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_completed_ended_at_idx": { + "name": "workflow_execution_logs_completed_ended_at_idx", + "columns": [ + { + "expression": "ended_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_execution_logs\".\"status\" = 'completed' AND \"workflow_execution_logs\".\"level\" = 'info' AND \"workflow_execution_logs\".\"ended_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_execution_logs_workflow_id_workflow_id_fk": { + "name": "workflow_execution_logs_workflow_id_workflow_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workflow_execution_logs_workspace_id_workspace_id_fk": { + "name": "workflow_execution_logs_workspace_id_workspace_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_execution_logs_state_snapshot_id_workflow_execution_snapshots_id_fk": { + "name": "workflow_execution_logs_state_snapshot_id_workflow_execution_snapshots_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow_execution_snapshots", + "columnsFrom": ["state_snapshot_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workflow_execution_logs_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "workflow_execution_logs_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow_deployment_version", + "columnsFrom": ["deployment_version_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_execution_snapshots": { + "name": "workflow_execution_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state_hash": { + "name": "state_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state_data": { + "name": "state_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_snapshots_workflow_id_idx": { + "name": "workflow_snapshots_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_hash_idx": { + "name": "workflow_snapshots_hash_idx", + "columns": [ + { + "expression": "state_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_workflow_hash_idx": { + "name": "workflow_snapshots_workflow_hash_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "state_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_created_at_idx": { + "name": "workflow_snapshots_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_execution_snapshots_workflow_id_workflow_id_fk": { + "name": "workflow_execution_snapshots_workflow_id_workflow_id_fk", + "tableFrom": "workflow_execution_snapshots", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_mcp_server": { + "name": "workflow_mcp_server", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_mcp_server_workspace_id_idx": { + "name": "workflow_mcp_server_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_created_by_idx": { + "name": "workflow_mcp_server_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_deleted_at_idx": { + "name": "workflow_mcp_server_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_workspace_deleted_partial_idx": { + "name": "workflow_mcp_server_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_mcp_server\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_mcp_server_workspace_id_workspace_id_fk": { + "name": "workflow_mcp_server_workspace_id_workspace_id_fk", + "tableFrom": "workflow_mcp_server", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_mcp_server_created_by_user_id_fk": { + "name": "workflow_mcp_server_created_by_user_id_fk", + "tableFrom": "workflow_mcp_server", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_mcp_tool": { + "name": "workflow_mcp_tool", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "server_id": { + "name": "server_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_description": { + "name": "tool_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "parameter_schema": { + "name": "parameter_schema", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "parameter_description_overrides": { + "name": "parameter_description_overrides", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'::json" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_mcp_tool_server_id_idx": { + "name": "workflow_mcp_tool_server_id_idx", + "columns": [ + { + "expression": "server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_workflow_id_idx": { + "name": "workflow_mcp_tool_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_server_workflow_unique": { + "name": "workflow_mcp_tool_server_workflow_unique", + "columns": [ + { + "expression": "server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow_mcp_tool\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_archived_at_partial_idx": { + "name": "workflow_mcp_tool_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_mcp_tool\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_mcp_tool_server_id_workflow_mcp_server_id_fk": { + "name": "workflow_mcp_tool_server_id_workflow_mcp_server_id_fk", + "tableFrom": "workflow_mcp_tool", + "tableTo": "workflow_mcp_server", + "columnsFrom": ["server_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_mcp_tool_workflow_id_workflow_id_fk": { + "name": "workflow_mcp_tool_workflow_id_workflow_id_fk", + "tableFrom": "workflow_mcp_tool", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_schedule": { + "name": "workflow_schedule", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deployment_version_id": { + "name": "deployment_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deployment_operation_id": { + "name": "deployment_operation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "block_id": { + "name": "block_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_ran_at": { + "name": "last_ran_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_queued_at": { + "name": "last_queued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "trigger_type": { + "name": "trigger_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "failed_count": { + "name": "failed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "infra_retry_count": { + "name": "infra_retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_failed_at": { + "name": "last_failed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'workflow'" + }, + "job_title": { + "name": "job_title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prompt": { + "name": "prompt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lifecycle": { + "name": "lifecycle", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'persistent'" + }, + "success_condition": { + "name": "success_condition", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "max_runs": { + "name": "max_runs", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "run_count": { + "name": "run_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "source_chat_id": { + "name": "source_chat_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_task_name": { + "name": "source_task_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_user_id": { + "name": "source_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_workspace_id": { + "name": "source_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_scope": { + "name": "secret_scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'all'" + }, + "mounted_secrets": { + "name": "mounted_secrets", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "job_history": { + "name": "job_history", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "contexts": { + "name": "contexts", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "excluded_dates": { + "name": "excluded_dates", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "ends_at": { + "name": "ends_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_schedule_workflow_block_deployment_unique": { + "name": "workflow_schedule_workflow_block_deployment_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow_schedule\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_workflow_deployment_idx": { + "name": "workflow_schedule_workflow_deployment_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_archived_at_partial_idx": { + "name": "workflow_schedule_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_schedule\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_workflow_schedule_on_source_workspace_id_source_t_c07f3bba6": { + "name": "idx_workflow_schedule_on_source_workspace_id_source_t_c07f3bba6", + "columns": [ + { + "expression": "source_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_due_workflow_idx": { + "name": "workflow_schedule_due_workflow_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_schedule\".\"archived_at\" IS NULL AND \"workflow_schedule\".\"status\" NOT IN ('disabled', 'completed') AND (\"workflow_schedule\".\"source_type\" = 'workflow' OR \"workflow_schedule\".\"source_type\" IS NULL)", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_due_job_idx": { + "name": "workflow_schedule_due_job_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_schedule\".\"archived_at\" IS NULL AND \"workflow_schedule\".\"status\" NOT IN ('disabled', 'completed') AND \"workflow_schedule\".\"source_type\" = 'job'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_schedule_workflow_id_workflow_id_fk": { + "name": "workflow_schedule_workflow_id_workflow_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "workflow_schedule_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow_deployment_version", + "columnsFrom": ["deployment_version_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_deployment_operation_id_workflow_deployment_operation_id_fk": { + "name": "workflow_schedule_deployment_operation_id_workflow_deployment_operation_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow_deployment_operation", + "columnsFrom": ["deployment_operation_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workflow_schedule_source_user_id_user_id_fk": { + "name": "workflow_schedule_source_user_id_user_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "user", + "columnsFrom": ["source_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_source_workspace_id_workspace_id_fk": { + "name": "workflow_schedule_source_workspace_id_workspace_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workspace", + "columnsFrom": ["source_workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_subflows": { + "name": "workflow_subflows", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_subflows_workflow_id_idx": { + "name": "workflow_subflows_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_subflows_workflow_type_idx": { + "name": "workflow_subflows_workflow_type_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_subflows_workflow_id_workflow_id_fk": { + "name": "workflow_subflows_workflow_id_workflow_id_fk", + "tableFrom": "workflow_subflows", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace": { + "name": "workspace", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'#33C482'" + }, + "logo_url": { + "name": "logo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_mode": { + "name": "workspace_mode", + "type": "workspace_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'grandfathered_shared'" + }, + "billed_account_user_id": { + "name": "billed_account_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "storage_used_bytes": { + "name": "storage_used_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "allow_personal_api_keys": { + "name": "allow_personal_api_keys", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "inbox_enabled": { + "name": "inbox_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "inbox_address": { + "name": "inbox_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "inbox_provider_id": { + "name": "inbox_provider_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "inbox_secret_scope": { + "name": "inbox_secret_scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'all'" + }, + "inbox_mounted_secrets": { + "name": "inbox_mounted_secrets", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "organization_assigned_at": { + "name": "organization_assigned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "forked_from_workspace_id": { + "name": "forked_from_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_owner_id_idx": { + "name": "workspace_owner_id_idx", + "columns": [ + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_organization_id_idx": { + "name": "workspace_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_mode_idx": { + "name": "workspace_mode_idx", + "columns": [ + { + "expression": "workspace_mode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_forked_from_workspace_id_idx": { + "name": "workspace_forked_from_workspace_id_idx", + "columns": [ + { + "expression": "forked_from_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_inbox_provider_id_idx": { + "name": "workspace_inbox_provider_id_idx", + "columns": [ + { + "expression": "inbox_provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace\".\"inbox_provider_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_owner_id_user_id_fk": { + "name": "workspace_owner_id_user_id_fk", + "tableFrom": "workspace", + "tableTo": "user", + "columnsFrom": ["owner_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_organization_id_organization_id_fk": { + "name": "workspace_organization_id_organization_id_fk", + "tableFrom": "workspace", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_billed_account_user_id_user_id_fk": { + "name": "workspace_billed_account_user_id_user_id_fk", + "tableFrom": "workspace", + "tableTo": "user", + "columnsFrom": ["billed_account_user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workspace_forked_from_workspace_id_workspace_id_fk": { + "name": "workspace_forked_from_workspace_id_workspace_id_fk", + "tableFrom": "workspace", + "tableTo": "workspace", + "columnsFrom": ["forked_from_workspace_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "workspace_storage_used_bytes_non_negative": { + "name": "workspace_storage_used_bytes_non_negative", + "value": "\"workspace\".\"storage_used_bytes\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.workspace_byok_keys": { + "name": "workspace_byok_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "encrypted_api_key": { + "name": "encrypted_api_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_byok_workspace_provider_idx": { + "name": "workspace_byok_workspace_provider_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_byok_keys_workspace_id_workspace_id_fk": { + "name": "workspace_byok_keys_workspace_id_workspace_id_fk", + "tableFrom": "workspace_byok_keys", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_byok_keys_created_by_user_id_fk": { + "name": "workspace_byok_keys_created_by_user_id_fk", + "tableFrom": "workspace_byok_keys", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_environment": { + "name": "workspace_environment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "variables": { + "name": "variables", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_environment_workspace_unique": { + "name": "workspace_environment_workspace_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_environment_workspace_id_workspace_id_fk": { + "name": "workspace_environment_workspace_id_workspace_id_fk", + "tableFrom": "workspace_environment", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file": { + "name": "workspace_file", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "uploaded_by": { + "name": "uploaded_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_file_workspace_id_idx": { + "name": "workspace_file_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_deleted_at_idx": { + "name": "workspace_file_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_workspace_deleted_partial_idx": { + "name": "workspace_file_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workspace_file\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_file_workspace_id_workspace_id_fk": { + "name": "workspace_file_workspace_id_workspace_id_fk", + "tableFrom": "workspace_file", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_file_uploaded_by_user_id_fk": { + "name": "workspace_file_uploaded_by_user_id_fk", + "tableFrom": "workspace_file", + "tableTo": "user", + "columnsFrom": ["uploaded_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workspace_file_key_unique": { + "name": "workspace_file_key_unique", + "nullsNotDistinct": false, + "columns": ["key"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_collab_state": { + "name": "workspace_file_collab_state", + "schema": "", + "columns": { + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "doc_state": { + "name": "doc_state", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "source_hash": { + "name": "source_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workspace_file_collab_state_file_id_workspace_files_id_fk": { + "name": "workspace_file_collab_state_file_id_workspace_files_id_fk", + "tableFrom": "workspace_file_collab_state", + "tableTo": "workspace_files", + "columnsFrom": ["file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_search_backfill": { + "name": "workspace_file_search_backfill", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "after_workspace_id": { + "name": "after_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "after_file_id": { + "name": "after_file_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_search_dispatch_queue": { + "name": "workspace_file_search_dispatch_queue", + "schema": "", + "columns": { + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "enqueued_at": { + "name": "enqueued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_dispatched_at": { + "name": "last_dispatched_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_file_search_dispatch_queue_schedule_idx": { + "name": "workspace_file_search_dispatch_queue_schedule_idx", + "columns": [ + { + "expression": "last_dispatched_at", + "isExpression": false, + "asc": true, + "nulls": "first" + }, + { + "expression": "enqueued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_file_search_queue_workspace_fk": { + "name": "workspace_file_search_queue_workspace_fk", + "tableFrom": "workspace_file_search_dispatch_queue", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_search_index": { + "name": "workspace_file_search_index", + "schema": "", + "columns": { + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_content_updated_at": { + "name": "source_content_updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "workspace_file_search_index_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "partial": { + "name": "partial", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "line_count": { + "name": "line_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "indexed_bytes": { + "name": "indexed_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "dispatched_at": { + "name": "dispatched_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_file_search_index_workspace_status_idx": { + "name": "workspace_file_search_index_workspace_status_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_content_updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_search_index_pending_dispatch_idx": { + "name": "workspace_file_search_index_pending_dispatch_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_content_updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workspace_file_search_index\".\"status\" = 'pending' AND \"workspace_file_search_index\".\"dispatched_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_search_index_active_dispatch_idx": { + "name": "workspace_file_search_index_active_dispatch_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "dispatched_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workspace_file_search_index\".\"status\" = 'pending' AND \"workspace_file_search_index\".\"dispatched_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_file_search_index_file_fk": { + "name": "workspace_file_search_index_file_fk", + "tableFrom": "workspace_file_search_index", + "tableTo": "workspace_files", + "columnsFrom": ["file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_file_search_index_workspace_fk": { + "name": "workspace_file_search_index_workspace_fk", + "tableFrom": "workspace_file_search_index", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "workspace_file_search_index_pk": { + "name": "workspace_file_search_index_pk", + "columns": ["file_id", "source_content_updated_at"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_search_segment": { + "name": "workspace_file_search_segment", + "schema": "", + "columns": { + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_content_updated_at": { + "name": "source_content_updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "line_number": { + "name": "line_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "segment_number": { + "name": "segment_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "segment_start": { + "name": "segment_start", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "line_length": { + "name": "line_length", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "workspace_file_search_segment_workspace_revision_idx": { + "name": "workspace_file_search_segment_workspace_revision_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_content_updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_search_segment_workspace_content_trgm_idx": { + "name": "workspace_file_search_segment_workspace_content_trgm_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "text_ops" + }, + { + "expression": "content", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "workspace_file_search_segment_file_fk": { + "name": "workspace_file_search_segment_file_fk", + "tableFrom": "workspace_file_search_segment", + "tableTo": "workspace_files", + "columnsFrom": ["file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_file_search_segment_workspace_fk": { + "name": "workspace_file_search_segment_workspace_fk", + "tableFrom": "workspace_file_search_segment", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "workspace_file_search_segment_pk": { + "name": "workspace_file_search_segment_pk", + "columns": ["file_id", "source_content_updated_at", "line_number", "segment_number"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_secret_provenance": { + "name": "workspace_file_secret_provenance", + "schema": "", + "columns": { + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "content_updated_at": { + "name": "content_updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entries": { + "name": "entries", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workspace_file_secret_provenance_file_id_workspace_files_id_fk": { + "name": "workspace_file_secret_provenance_file_id_workspace_files_id_fk", + "tableFrom": "workspace_file_secret_provenance", + "tableTo": "workspace_files", + "columnsFrom": ["file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "workspace_file_secret_provenance_status_check": { + "name": "workspace_file_secret_provenance_status_check", + "value": "\"workspace_file_secret_provenance\".\"status\" IN ('exact', 'unknown', 'unrecorded')" + } + }, + "isRLSEnabled": false + }, + "public.workspace_files": { + "name": "workspace_files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "folder_id": { + "name": "folder_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "context": { + "name": "context", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "original_name": { + "name": "original_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size_bytes": { + "name": "size_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "width": { + "name": "width", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "height": { + "name": "height", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "content_updated_at": { + "name": "content_updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "secret_provenance_version": { + "name": "secret_provenance_version", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workspace_files_key_active_unique": { + "name": "workspace_files_key_active_unique", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_files\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_workspace_folder_name_active_unique": { + "name": "workspace_files_workspace_folder_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"folder_id\", '')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "original_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_files\".\"deleted_at\" IS NULL AND \"workspace_files\".\"context\" = 'workspace' AND \"workspace_files\".\"workspace_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_chat_display_name_unique": { + "name": "workspace_files_chat_display_name_unique", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_files\".\"context\" = 'mothership' AND \"workspace_files\".\"chat_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_organization_id_idx": { + "name": "workspace_files_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_key_idx": { + "name": "workspace_files_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_user_id_idx": { + "name": "workspace_files_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_workspace_id_idx": { + "name": "workspace_files_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_folder_id_idx": { + "name": "workspace_files_folder_id_idx", + "columns": [ + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_context_idx": { + "name": "workspace_files_context_idx", + "columns": [ + { + "expression": "context", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_chat_id_idx": { + "name": "workspace_files_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_deleted_at_idx": { + "name": "workspace_files_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_workspace_deleted_partial_idx": { + "name": "workspace_files_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workspace_files\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_files_user_id_user_id_fk": { + "name": "workspace_files_user_id_user_id_fk", + "tableFrom": "workspace_files", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_files_workspace_id_workspace_id_fk": { + "name": "workspace_files_workspace_id_workspace_id_fk", + "tableFrom": "workspace_files", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_files_organization_id_organization_id_fk": { + "name": "workspace_files_organization_id_organization_id_fk", + "tableFrom": "workspace_files", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_files_folder_id_folder_id_fk": { + "name": "workspace_files_folder_id_folder_id_fk", + "tableFrom": "workspace_files", + "tableTo": "folder", + "columnsFrom": ["folder_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_files_chat_id_copilot_chats_id_fk": { + "name": "workspace_files_chat_id_copilot_chats_id_fk", + "tableFrom": "workspace_files", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "workspace_files_organization_binding_check": { + "name": "workspace_files_organization_binding_check", + "value": "\"workspace_files\".\"organization_id\" IS NULL OR (\"workspace_files\".\"workspace_id\" IS NULL AND \"workspace_files\".\"context\" = 'knowledge-base' AND \"workspace_files\".\"folder_id\" IS NULL AND \"workspace_files\".\"chat_id\" IS NULL)" + } + }, + "isRLSEnabled": false + }, + "public.workspace_fork_block_map": { + "name": "workspace_fork_block_map", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "child_workspace_id": { + "name": "child_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_workflow_id": { + "name": "parent_workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_block_id": { + "name": "parent_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "child_workflow_id": { + "name": "child_workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "child_block_id": { + "name": "child_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_fork_block_map_child_ws_parent_unique": { + "name": "workspace_fork_block_map_child_ws_parent_unique", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_block_map_child_ws_child_unique": { + "name": "workspace_fork_block_map_child_ws_child_unique", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "child_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_block_map_child_ws_parent_wf_idx": { + "name": "workspace_fork_block_map_child_ws_parent_wf_idx", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_block_map_child_ws_child_wf_idx": { + "name": "workspace_fork_block_map_child_ws_child_wf_idx", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "child_workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_fork_block_map_child_workspace_id_workspace_id_fk": { + "name": "workspace_fork_block_map_child_workspace_id_workspace_id_fk", + "tableFrom": "workspace_fork_block_map", + "tableTo": "workspace", + "columnsFrom": ["child_workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_fork_dependent_value": { + "name": "workspace_fork_dependent_value", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "child_workspace_id": { + "name": "child_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_workflow_id": { + "name": "target_workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_block_id": { + "name": "target_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sub_block_key": { + "name": "sub_block_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_fork_dependent_value_child_ws_wf_idx": { + "name": "workspace_fork_dependent_value_child_ws_wf_idx", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_dependent_value_field_unique": { + "name": "workspace_fork_dependent_value_field_unique", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sub_block_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_fork_dependent_value_child_workspace_id_workspace_id_fk": { + "name": "workspace_fork_dependent_value_child_workspace_id_workspace_id_fk", + "tableFrom": "workspace_fork_dependent_value", + "tableTo": "workspace", + "columnsFrom": ["child_workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_fork_promote_run": { + "name": "workspace_fork_promote_run", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "child_workspace_id": { + "name": "child_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_workspace_id": { + "name": "source_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_workspace_id": { + "name": "target_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "direction": { + "name": "direction", + "type": "workspace_fork_promote_direction", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "snapshot": { + "name": "snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_fork_promote_run_child_ws_target_unique": { + "name": "workspace_fork_promote_run_child_ws_target_unique", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_promote_run_target_ws_idx": { + "name": "workspace_fork_promote_run_target_ws_idx", + "columns": [ + { + "expression": "target_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_fork_promote_run_child_workspace_id_workspace_id_fk": { + "name": "workspace_fork_promote_run_child_workspace_id_workspace_id_fk", + "tableFrom": "workspace_fork_promote_run", + "tableTo": "workspace", + "columnsFrom": ["child_workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_fork_promote_run_created_by_user_id_fk": { + "name": "workspace_fork_promote_run_created_by_user_id_fk", + "tableFrom": "workspace_fork_promote_run", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_fork_resource_map": { + "name": "workspace_fork_resource_map", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "child_workspace_id": { + "name": "child_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "workspace_fork_resource_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "parent_resource_id": { + "name": "parent_resource_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "child_resource_id": { + "name": "child_resource_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_fork_resource_map_child_ws_idx": { + "name": "workspace_fork_resource_map_child_ws_idx", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_resource_map_child_ws_type_idx": { + "name": "workspace_fork_resource_map_child_ws_type_idx", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_resource_map_child_type_parent_unique": { + "name": "workspace_fork_resource_map_child_type_parent_unique", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_fork_resource_map_child_workspace_id_workspace_id_fk": { + "name": "workspace_fork_resource_map_child_workspace_id_workspace_id_fk", + "tableFrom": "workspace_fork_resource_map", + "tableTo": "workspace", + "columnsFrom": ["child_workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_fork_resource_map_created_by_user_id_fk": { + "name": "workspace_fork_resource_map_created_by_user_id_fk", + "tableFrom": "workspace_fork_resource_map", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_operation_receipt": { + "name": "workspace_operation_receipt", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_hash": { + "name": "request_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "report": { + "name": "report", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_operation_receipt_request_unique": { + "name": "workspace_operation_receipt_request_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_operation_receipt_workspace_created_idx": { + "name": "workspace_operation_receipt_workspace_created_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_operation_receipt_workspace_id_workspace_id_fk": { + "name": "workspace_operation_receipt_workspace_id_workspace_id_fk", + "tableFrom": "workspace_operation_receipt", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_sandbox": { + "name": "workspace_sandbox", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "language": { + "name": "language", + "type": "sandbox_language", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "dependencies": { + "name": "dependencies", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "cli_tools": { + "name": "cli_tools", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "system_packages": { + "name": "system_packages", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "spec_hash": { + "name": "spec_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_sandbox_workspace_name_unique": { + "name": "workspace_sandbox_workspace_name_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_sandbox_workspace_idx": { + "name": "workspace_sandbox_workspace_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_sandbox_spec_hash_idx": { + "name": "workspace_sandbox_spec_hash_idx", + "columns": [ + { + "expression": "spec_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_sandbox_workspace_id_workspace_id_fk": { + "name": "workspace_sandbox_workspace_id_workspace_id_fk", + "tableFrom": "workspace_sandbox", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_sandbox_created_by_user_id_fk": { + "name": "workspace_sandbox_created_by_user_id_fk", + "tableFrom": "workspace_sandbox", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.academy_cert_status": { + "name": "academy_cert_status", + "schema": "public", + "values": ["active", "revoked", "expired"] + }, + "public.background_work_kind": { + "name": "background_work_kind", + "schema": "public", + "values": ["deployment_side_effects", "fork_content_copy", "fork_sync", "fork_rollback"] + }, + "public.background_work_status_value": { + "name": "background_work_status_value", + "schema": "public", + "values": ["pending", "processing", "completed", "completed_with_warnings", "failed"] + }, + "public.billing_blocked_reason": { + "name": "billing_blocked_reason", + "schema": "public", + "values": ["payment_failed", "dispute"] + }, + "public.billing_entity_type": { + "name": "billing_entity_type", + "schema": "public", + "values": ["user", "organization"] + }, + "public.chat_type": { + "name": "chat_type", + "schema": "public", + "values": ["mothership", "copilot"] + }, + "public.copilot_async_tool_status": { + "name": "copilot_async_tool_status", + "schema": "public", + "values": ["pending", "running", "completed", "failed", "cancelled", "delivered"] + }, + "public.copilot_run_status": { + "name": "copilot_run_status", + "schema": "public", + "values": ["active", "paused_waiting_for_tool", "resuming", "complete", "error", "cancelled"] + }, + "public.copilot_tool_permission_decision": { + "name": "copilot_tool_permission_decision", + "schema": "public", + "values": ["allow", "allow_chat", "always_allow", "skip"] + }, + "public.credential_group_enrollment_status": { + "name": "credential_group_enrollment_status", + "schema": "public", + "values": ["invited", "delivery_failed", "in_progress", "completed", "revoked"] + }, + "public.credential_group_status": { + "name": "credential_group_status", + "schema": "public", + "values": ["active", "disabled"] + }, + "public.credential_member_role": { + "name": "credential_member_role", + "schema": "public", + "values": ["admin", "member"] + }, + "public.credential_member_status": { + "name": "credential_member_status", + "schema": "public", + "values": ["active", "pending", "revoked"] + }, + "public.credential_type": { + "name": "credential_type", + "schema": "public", + "values": [ + "oauth", + "managed_oauth", + "managed_mcp", + "env_workspace", + "env_personal", + "service_account", + "personal_token" + ] + }, + "public.data_drain_cadence": { + "name": "data_drain_cadence", + "schema": "public", + "values": ["hourly", "daily"] + }, + "public.data_drain_destination": { + "name": "data_drain_destination", + "schema": "public", + "values": ["s3", "gcs", "azure_blob", "datadog", "bigquery", "snowflake", "webhook"] + }, + "public.data_drain_run_status": { + "name": "data_drain_run_status", + "schema": "public", + "values": ["running", "success", "failed"] + }, + "public.data_drain_run_trigger": { + "name": "data_drain_run_trigger", + "schema": "public", + "values": ["cron", "manual"] + }, + "public.data_drain_source": { + "name": "data_drain_source", + "schema": "public", + "values": ["workflow_logs", "job_logs", "audit_logs", "copilot_chats", "copilot_runs"] + }, + "public.execution_large_value_reference_source": { + "name": "execution_large_value_reference_source", + "schema": "public", + "values": ["execution_log", "paused_snapshot"] + }, + "public.folder_resource_type": { + "name": "folder_resource_type", + "schema": "public", + "values": ["workflow", "file", "knowledge_base", "table"] + }, + "public.invitation_kind": { + "name": "invitation_kind", + "schema": "public", + "values": ["organization", "workspace"] + }, + "public.invitation_membership_intent": { + "name": "invitation_membership_intent", + "schema": "public", + "values": ["internal", "external"] + }, + "public.invitation_status": { + "name": "invitation_status", + "schema": "public", + "values": ["pending", "accepted", "rejected", "cancelled", "expired"] + }, + "public.managed_oauth_credential_status": { + "name": "managed_oauth_credential_status", + "schema": "public", + "values": ["active", "needs_reauth", "revoked"] + }, + "public.permission_type": { + "name": "permission_type", + "schema": "public", + "values": ["admin", "write", "read"] + }, + "public.sandbox_image_status": { + "name": "sandbox_image_status", + "schema": "public", + "values": ["pending", "building", "ready", "failed"] + }, + "public.sandbox_language": { + "name": "sandbox_language", + "schema": "public", + "values": ["javascript", "python"] + }, + "public.secret_usage_scope": { + "name": "secret_usage_scope", + "schema": "public", + "values": ["workspace", "personal"] + }, + "public.secret_usage_source": { + "name": "secret_usage_source", + "schema": "public", + "values": ["workflow", "copilot", "mcp"] + }, + "public.upload_session_method": { + "name": "upload_session_method", + "schema": "public", + "values": ["put", "multipart"] + }, + "public.upload_session_provider": { + "name": "upload_session_provider", + "schema": "public", + "values": ["local", "s3", "blob", "gcs"] + }, + "public.upload_session_purpose": { + "name": "upload_session_purpose", + "schema": "public", + "values": [ + "workspace_file", + "table_import", + "knowledge_document", + "profile_picture", + "workspace_logo", + "organization_logo", + "mothership_attachment", + "execution_attachment" + ] + }, + "public.upload_session_status": { + "name": "upload_session_status", + "schema": "public", + "values": [ + "uploading", + "completing", + "finalizing", + "completed", + "aborting", + "aborted", + "failed", + "expired" + ] + }, + "public.usage_log_category": { + "name": "usage_log_category", + "schema": "public", + "values": ["model", "fixed", "tool", "model_unbilled"] + }, + "public.usage_log_source": { + "name": "usage_log_source", + "schema": "public", + "values": [ + "workflow", + "wand", + "copilot", + "workspace-chat", + "mcp_copilot", + "mothership_block", + "knowledge-base", + "voice-input", + "enrichment", + "voice-output", + "api-tool" + ] + }, + "public.workspace_file_search_index_status": { + "name": "workspace_file_search_index_status", + "schema": "public", + "values": ["pending", "ready", "skipped", "failed"] + }, + "public.workspace_fork_promote_direction": { + "name": "workspace_fork_promote_direction", + "schema": "public", + "values": ["push", "pull"] + }, + "public.workspace_fork_resource_type": { + "name": "workspace_fork_resource_type", + "schema": "public", + "values": [ + "workflow", + "oauth_credential", + "service_account_credential", + "env_var", + "table", + "knowledge_base", + "knowledge_document", + "file", + "file_folder", + "mcp_server", + "workflow_mcp_server", + "custom_block", + "custom_tool", + "skill", + "sandbox" + ] + }, + "public.workspace_mode": { + "name": "workspace_mode", + "schema": "public", + "values": ["personal", "organization", "grandfathered_shared"] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/packages/db/migrations/meta/_journal.json b/packages/db/migrations/meta/_journal.json index dd7fe8327b2..032acfbe213 100644 --- a/packages/db/migrations/meta/_journal.json +++ b/packages/db/migrations/meta/_journal.json @@ -2486,6 +2486,13 @@ "when": 1789609714073, "tag": "0355_workflow_activity_covering_index", "breakpoints": true + }, + { + "idx": 356, + "version": "7", + "when": 1789672641125, + "tag": "0356_connector_partition_progress", + "breakpoints": true } ] } diff --git a/packages/db/schema.ts b/packages/db/schema.ts index 1e43fea47af..0be0f42f8d2 100644 --- a/packages/db/schema.ts +++ b/packages/db/schema.ts @@ -5698,6 +5698,67 @@ export const knowledgeConnector = pgTable( }) ) +/** Bounded provider partitions, committed atomically with their owning connector listing checkpoint. */ +export const knowledgeConnectorPartition = pgTable( + 'knowledge_connector_partition', + { + connectorId: text('connector_id') + .notNull() + .references(() => knowledgeConnector.id, { onDelete: 'cascade' }), + partitionKey: text('partition_key').notNull(), + generationId: text('generation_id').notNull(), + context: jsonb('context').$type>().notNull(), + cursor: text('cursor'), + status: text('status').notNull().default('pending'), + attempts: integer('attempts').notNull().default(0), + retryAt: timestamp('retry_at').notNull().defaultNow(), + lastServedAt: timestamp('last_served_at'), + failure: jsonb('failure').$type>(), + permissionCursor: text('permission_cursor'), + permissionAttempts: integer('permission_attempts').notNull().default(0), + permissionRetryAt: timestamp('permission_retry_at').notNull(), + permissionLastServedAt: timestamp('permission_last_served_at'), + permissionStartedAt: timestamp('permission_started_at'), + permissionFailure: jsonb('permission_failure').$type>(), + }, + (table) => ({ + pk: primaryKey({ name: 'kcp_pk', columns: [table.connectorId, table.partitionKey] }), + contentDueIdx: index('kcp_content_due_idx').on( + table.connectorId, + table.generationId, + table.status, + table.retryAt, + table.lastServedAt + ), + permissionDueIdx: index('kcp_permission_due_idx').on( + table.connectorId, + table.generationId, + table.permissionRetryAt, + table.permissionLastServedAt + ), + partitionKeyCheck: check( + 'kcp_partition_key_check', + sql`octet_length(${table.partitionKey}) BETWEEN 1 AND 1024` + ), + contextCheck: check( + 'kcp_context_check', + sql`jsonb_typeof(${table.context}) = 'object' AND octet_length(${table.context}::text) <= 16384` + ), + statusCheck: check( + 'kcp_status_check', + sql`${table.status} IN ('pending', 'complete', 'blocked')` + ), + cursorCheck: check( + 'kcp_cursor_check', + sql`(${table.cursor} IS NULL OR octet_length(${table.cursor}) <= 393216) AND (${table.permissionCursor} IS NULL OR octet_length(${table.permissionCursor}) <= 393216)` + ), + attemptsCheck: check( + 'kcp_attempts_check', + sql`${table.attempts} >= 0 AND ${table.permissionAttempts} >= 0` + ), + }) +) + /** Private provider configuration; metadata reads never materialize the larger normalized payload. */ export const knowledgeConnectorPermissionSnapshot = pgTable( 'knowledge_connector_permission_snapshot', diff --git a/packages/testing/src/mocks/schema.mock.ts b/packages/testing/src/mocks/schema.mock.ts index 625dbab0d29..ee91d040c60 100644 --- a/packages/testing/src/mocks/schema.mock.ts +++ b/packages/testing/src/mocks/schema.mock.ts @@ -1740,6 +1740,24 @@ export const schemaMock = { subjectToken: 'knowledgeExternalGroupMember.subjectToken', createdAt: 'knowledgeExternalGroupMember.createdAt', }, + knowledgeConnectorPartition: { + connectorId: 'knowledgeConnectorPartition.connectorId', + partitionKey: 'knowledgeConnectorPartition.partitionKey', + generationId: 'knowledgeConnectorPartition.generationId', + context: 'knowledgeConnectorPartition.context', + cursor: 'knowledgeConnectorPartition.cursor', + status: 'knowledgeConnectorPartition.status', + attempts: 'knowledgeConnectorPartition.attempts', + retryAt: 'knowledgeConnectorPartition.retryAt', + lastServedAt: 'knowledgeConnectorPartition.lastServedAt', + failure: 'knowledgeConnectorPartition.failure', + permissionCursor: 'knowledgeConnectorPartition.permissionCursor', + permissionAttempts: 'knowledgeConnectorPartition.permissionAttempts', + permissionRetryAt: 'knowledgeConnectorPartition.permissionRetryAt', + permissionLastServedAt: 'knowledgeConnectorPartition.permissionLastServedAt', + permissionStartedAt: 'knowledgeConnectorPartition.permissionStartedAt', + permissionFailure: 'knowledgeConnectorPartition.permissionFailure', + }, knowledgeConnectorMember: { id: 'knowledgeConnectorMember.id', workspaceId: 'knowledgeConnectorMember.workspaceId', From 0b893d73c09856c35878f34b607a992c58de5df3 Mon Sep 17 00:00:00 2001 From: Waleed Date: Thu, 17 Sep 2026 12:48:49 -0700 Subject: [PATCH 10/15] feat(atlassian): resolve dynamic scopes and index Confluence attachments (#7935) * feat(atlassian): resolve dynamic scopes and index Confluence attachments * fix(atlassian): reject mixed All scopes and run permission tests in CI * fix(ci): isolate Confluence migration test database --- .github/workflows/test-build.yml | 20 + .../content/docs/knowledgebase/connectors.mdx | 4 +- apps/docs/content/docs/search/confluence.mdx | 16 +- apps/docs/content/docs/search/jira.mdx | 6 +- .../connector-selector-field.test.tsx | 58 +- .../connector-selector-field.tsx | 34 +- .../connector-selector-field.ui.test.tsx | 109 ++-- .../use-connector-config-fields.test.tsx | 34 + apps/sim/connectors/all-selection.test.ts | 158 +++++ .../connectors/confluence/attachments.test.ts | 615 ++++++++++++++++++ apps/sim/connectors/confluence/attachments.ts | 478 ++++++++++++++ .../connectors/confluence/bulk-spaces.test.ts | 6 +- .../connectors/confluence/confluence.test.ts | 263 +++++--- apps/sim/connectors/confluence/confluence.ts | 326 ++++++---- apps/sim/connectors/confluence/meta.ts | 4 + .../connectors/confluence/permissions.test.ts | 64 +- apps/sim/connectors/confluence/permissions.ts | 46 +- apps/sim/connectors/jira/jira.test.ts | 25 + apps/sim/connectors/jira/jira.ts | 15 +- apps/sim/connectors/jira/meta.ts | 2 + apps/sim/connectors/selection.ts | 34 + apps/sim/connectors/types.ts | 22 +- .../confluence-identity.integration.ts | 22 +- .../directory-sync.integration.ts | 197 +++++- .../access/confluence-permissions.ts | 7 + .../access/confluence-space-groups.ts | 6 + .../access/group-membership.integration.ts | 79 +++ .../knowledge/access/group-membership.test.ts | 22 + .../lib/knowledge/access/group-membership.ts | 92 +++ .../access/predicate.postgres.test.ts | 32 + apps/sim/lib/knowledge/access/predicate.ts | 38 +- apps/sim/lib/knowledge/access/scope.test.ts | 32 +- apps/sim/lib/knowledge/access/scope.ts | 25 +- apps/sim/lib/knowledge/access/tokens.test.ts | 42 ++ apps/sim/lib/knowledge/access/tokens.ts | 23 +- .../application/personal-source-setup.test.ts | 40 ++ .../application/personal-source-setup.ts | 7 + .../connectors/external-group-sync.test.ts | 13 + .../connectors/external-group-sync.ts | 60 +- .../connectors/member-access.test.ts | 29 + .../connectors/sync-content-pass.test.ts | 41 ++ .../lib/knowledge/connectors/sync-engine.ts | 43 +- .../orchestration/connectors.test.ts | 74 +++ .../lib/knowledge/orchestration/connectors.ts | 16 +- .../lib/sim-search/personal-source-setup.ts | 2 +- .../lib/sim-search/source-identity.test.ts | 12 + apps/sim/lib/sim-search/source-identity.ts | 2 + packages/db/schema.ts | 4 +- 48 files changed, 2925 insertions(+), 374 deletions(-) create mode 100644 apps/sim/connectors/all-selection.test.ts create mode 100644 apps/sim/connectors/confluence/attachments.test.ts create mode 100644 apps/sim/connectors/confluence/attachments.ts create mode 100644 apps/sim/connectors/selection.ts create mode 100644 apps/sim/lib/knowledge/access/confluence-space-groups.ts create mode 100644 apps/sim/lib/knowledge/access/group-membership.integration.ts create mode 100644 apps/sim/lib/knowledge/access/group-membership.test.ts create mode 100644 apps/sim/lib/knowledge/access/group-membership.ts diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml index af187705e13..c7c8b5de7b2 100644 --- a/.github/workflows/test-build.yml +++ b/.github/workflows/test-build.yml @@ -245,6 +245,26 @@ jobs: lib/knowledge/__integration__/connector-upload.integration.ts lib/uploads/contexts/organization-logo/application.integration.ts + - name: Verify Confluence identity and directory sync in PostgreSQL + working-directory: apps/sim + env: + KNOWLEDGE_ACL_TEST_DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/sim_auth_scim + run: >- + bunx vitest run --mode integration + lib/knowledge/__integration__/confluence-identity.integration.ts + lib/knowledge/__integration__/directory-sync.integration.ts + + - name: Verify Confluence audience migrations and permission queries in PostgreSQL + working-directory: apps/sim + env: + KNOWLEDGE_ACL_TEST_DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/sim_acl_test + run: | + bun -e 'import postgres from "postgres"; const sql = postgres(process.env.DATABASE_URL); await sql`CREATE DATABASE sim_acl_test`; await sql.end()' + bunx vitest run --mode integration lib/knowledge/access/group-membership.integration.ts + bunx vitest run \ + lib/knowledge/access/predicate.postgres.test.ts \ + lib/knowledge/connectors/external-directory.postgres.test.ts + test-build: name: Lint and Test runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-latest' }} diff --git a/apps/docs/content/docs/knowledgebase/connectors.mdx b/apps/docs/content/docs/knowledgebase/connectors.mdx index e3624676668..8aade70fc02 100644 --- a/apps/docs/content/docs/knowledgebase/connectors.mdx +++ b/apps/docs/content/docs/knowledgebase/connectors.mdx @@ -78,7 +78,7 @@ Each connector has source-specific fields that control what gets synced. Example - **Notion** — sync an entire workspace, a specific database, or a single page tree - **GitHub** — specify a repository, branch, and optional file extension filter -- **Confluence** — enter your Atlassian domain and optionally filter by space key or content type +- **Confluence** — enter your Atlassian domain and choose spaces, or **All** for all spaces accessible at each sync. Optionally filter by content type or label. PDF and Word (`.docx`, Word 97–2003 `.doc`) attachments on matching pages and blog posts are included as separate documents. - **Azure DevOps** — choose what to sync (wiki pages, work items, repository files, or all), with optional work item type/state filters, a custom WIQL query, and repository/branch/path filters - **Amazon S3** — point at a bucket with an optional key prefix and a customizable file extension allowlist; S3-compatible stores (Cloudflare R2, MinIO) are supported via a custom endpoint - **YouTube** — sync a channel (by `@handle` or ID) or playlist, with an optional published-after date filter and the option to exclude Shorts @@ -88,6 +88,8 @@ Each connector has source-specific fields that control what gets synced. Example Configuration is validated on save — if a repository doesn't exist or a domain is unreachable, you'll see an error immediately. +Confluence attachment indexing requires `read:attachment:confluence`. For a service account, include it when creating the scoped API token; see the [Confluence scope list](/search/confluence#using-a-service-account). Attachments are checked even when the parent page has not changed. Files over 100 MB appear as skipped; convert Word 6/95 files to `.docx` before attaching them. +
    diff --git a/apps/docs/content/docs/search/confluence.mdx b/apps/docs/content/docs/search/confluence.mdx index d07af6163bf..aab0393b058 100644 --- a/apps/docs/content/docs/search/confluence.mdx +++ b/apps/docs/content/docs/search/confluence.mdx @@ -7,7 +7,7 @@ import { Callout } from 'fumadocs-ui/components/callout' import { Step, Steps } from 'fumadocs-ui/components/steps' import { Image } from '@/components/ui/image' -Search pages and blog posts from selected Confluence Cloud spaces. A Sim organization admin enables Confluence; **each teammate connects their own account**. +Search pages, blog posts, and their PDF and Word attachments from selected Confluence Cloud spaces. A Sim organization admin enables Confluence; **each teammate connects their own account**. | Method | How it works | | --- | --- | @@ -38,7 +38,9 @@ Open **Settings → Sources → Add source** and select **Confluence**. This ope ### Choose the account and spaces -Under **Service account**, select a service account or [add one](#using-a-service-account). Enter the same **Confluence site** as the credential, then choose **Spaces**. **All** in the dropdown selects every space the account can currently browse; newly created spaces are not added automatically. Clear the picker search before selecting all. +Under **Service account**, select a service account or [add one](#using-a-service-account). Enter the same **Confluence site** as the credential, then choose **Spaces**. **All** in the dropdown includes every space the syncing account can access at each sync, including newly accessible spaces. Clear the picker search before selecting all. + +If you selected all spaces before this behavior was introduced, reselect **All** and save. Previously saved selections remain a fixed list of spaces. To enter comma-separated keys such as `ENG, PRODUCT`, use the switch beside **Spaces**. Switching between the picker and manual entry keeps your selection. @@ -63,7 +65,7 @@ After an admin configures Confluence, open **Integrations** and select **Connect If Confluence is allowed but no source exists, select **Connect** beside Confluence. To add another site later, open the Confluence row’s actions menu (**…**) and select **Add Confluence site**: 1. Open **Your account** and select a saved account or **Connect Confluence account**. Authorize using the Atlassian email matching your verified Sim email. -2. Enter the hostname under **Atlassian site**, then choose **Spaces**. Use **All** in the dropdown for the complete current list, or the arrows beside **Spaces** to enter comma-separated keys. You can select up to 1,000 spaces in this form. +2. Enter the hostname under **Atlassian site**, then choose **Spaces**. Use **All** in the dropdown for all spaces accessible at each sync, or the arrows beside **Spaces** to enter comma-separated keys. You can select up to 1,000 individual spaces in this form. 3. Select **Connect & Sync**. Sim saves the selected scope and starts indexing with your account. ({ import { ConnectorSelectorField } from '@/app/workspace/[workspaceId]/knowledge/[id]/components/connector-selector-field/connector-selector-field' interface ComboboxCallbacks { - options: { value: string; label: string; hidden?: boolean; onSelect?: () => void }[] + options: { + value: string + label: string + hidden?: boolean + selected?: boolean + onSelect?: () => void + }[] disabled: boolean onChange?: (value: string) => void onMultiSelectChange?: (value: string[]) => void } +it('stores dynamic All without enumerating a snapshot and lets users select individual items again', async () => { + const field = { + id: 'spaces', + title: 'Spaces', + type: 'selector', + selectorKey: 'confluence.spaces', + multi: true, + allowSelectAll: true, + selectAllValue: '*', + } as const + const root = createRoot(document.createElement('div')) + const render = async (value: string[]) => + act(async () => + root.render( + + ) + ) + try { + await render([]) + await act(async () => + mocks.combobox.mock + .lastCall![0].options.find((option) => option.label === 'All') + ?.onSelect?.() + ) + expect(mocks.change).toHaveBeenLastCalledWith(['*'], [{ id: '*', label: 'All' }]) + expect(mocks.loadAll).not.toHaveBeenCalled() + await render(['*']) + await act(async () => mocks.combobox.mock.lastCall![0].onMultiSelectChange?.(['*', 'folder-b'])) + expect(mocks.change).toHaveBeenLastCalledWith( + ['folder-b'], + [{ id: 'folder-b', label: 'Company docs' }] + ) + await render(['*', 'folder-b']) + const all = mocks.combobox.mock.lastCall![0].options.find((option) => option.label === 'All') + await act(async () => all?.onSelect?.()) + expect(mocks.change).toHaveBeenLastCalledWith(['*'], [{ id: '*', label: 'All' }]) + } finally { + await act(async () => root.unmount()) + vi.clearAllMocks() + } +}) + beforeEach(() => { vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true) }) diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connector-selector-field/connector-selector-field.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connector-selector-field/connector-selector-field.tsx index bdd170f7e50..eae49ed34e5 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connector-selector-field/connector-selector-field.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connector-selector-field/connector-selector-field.tsx @@ -139,8 +139,11 @@ export function ConnectorSelectorField({ const singleValue = Array.isArray(value) ? value[0] : value const selectedIds = useMemo( - () => (Array.isArray(value) ? value : value ? [value] : []).filter(Boolean), - [value] + () => + (Array.isArray(value) ? value : value ? [value] : []).filter( + (id) => Boolean(id) && id !== field.selectAllValue + ), + [value, field.selectAllValue] ) const missingSelectedIds = useMemo(() => { const loadedIds = new Set(options.map((option) => option.id)) @@ -197,6 +200,9 @@ export function ConnectorSelectorField({ }, [options, selectedOptions, searchedOption, selectedLabels, selectedIds]) const handleChange = (nextValue: ConfigFieldValue) => { + if (Array.isArray(nextValue) && field.selectAllValue) { + nextValue = nextValue.filter((id) => id !== field.selectAllValue) + } bulkGenerationRef.current += 1 setBulkError(null) const ids = new Set(Array.isArray(nextValue) ? nextValue : nextValue ? [nextValue] : []) @@ -214,18 +220,26 @@ export function ConnectorSelectorField({ const hasSearch = searchTerm.trim().length > 0 || debouncedSearch.length > 0 const selectedIdSet = new Set(selectedIds) - const allSelected = - !hasMore && - !truncated && - options.length > 0 && - selectedIds.length === options.length && - options.every((option) => selectedIdSet.has(option.id)) + const values = Array.isArray(value) ? value : [value] + const allSelected = field.selectAllValue + ? values.length === 1 && values[0] === field.selectAllValue + : !hasMore && + !truncated && + options.length > 0 && + selectedIds.length === options.length && + options.every((option) => selectedIdSet.has(option.id)) const selectAll = async () => { if (!isEnabled || hasSearch || isFetching || isLoadingAll) return if (allSelected) { handleChange([]) return } + if (field.selectAllValue) { + bulkGenerationRef.current += 1 + setBulkError(null) + onChange([field.selectAllValue], [{ id: field.selectAllValue, label: 'All' }]) + return + } const generation = ++bulkGenerationRef.current setBulkError(null) const result = await loadAll() @@ -265,10 +279,10 @@ export function ConnectorSelectorField({ aria-label={field.title} multiSelect options={ - field.allowSelectAll && (options.length > 0 || hasMore) + field.allowSelectAll && (options.length > 0 || hasMore || allSelected) ? [ { - value: '', + value: field.selectAllValue ?? '', label: 'All', disabled: !isEnabled || hasSearch || isFetching || isLoadingAll, onSelect: () => void selectAll(), diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connector-selector-field/connector-selector-field.ui.test.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connector-selector-field/connector-selector-field.ui.test.tsx index 2f966afe39d..e3595c5094c 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connector-selector-field/connector-selector-field.ui.test.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connector-selector-field/connector-selector-field.ui.test.tsx @@ -39,7 +39,11 @@ vi.mock('@/hooks/queries/selectors', () => ({ import { ConnectorSelectorField } from '@/app/workspace/[workspaceId]/knowledge/[id]/components/connector-selector-field/connector-selector-field' -function ControlledSelector() { +interface ControlledSelectorProps { + dynamicAll?: boolean +} + +function ControlledSelector({ dynamicAll = false }: ControlledSelectorProps) { const [value, setValue] = useState([]) return ( { mocks.error = null }) -it('selects all pages with the keyboard, announces selection, and toggles it off', async () => { - vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true) - const originalScroll = HTMLElement.prototype.scrollIntoView - HTMLElement.prototype.scrollIntoView = vi.fn() - mocks.loadAll.mockResolvedValue({ - status: 'complete', - options: [ - { id: 'ENG', label: 'Engineering' }, - { id: 'OPS', label: 'Operations' }, - ], - }) - const container = document.createElement('div') - document.body.appendChild(container) - const root = createRoot(container) - try { - await act(async () => root.render()) - expect(container.textContent).not.toContain('Select all') - expect(container.textContent).not.toContain('Clear') - const trigger = container.querySelector('[role="combobox"]') - expect(trigger).not.toBeNull() - await act(async () => - trigger?.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true })) - ) - expect(document.querySelector('[role="option"]')?.textContent).toBe('All') - await act(async () => - trigger?.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true })) - ) - expect(document.querySelector('[role="option"]')?.getAttribute('aria-selected')).toBe('true') - expect(mocks.loadAll).toHaveBeenCalledOnce() - expect(mocks.change).toHaveBeenCalledWith( - ['ENG', 'OPS'], - [ +it.each([false, true])( + 'selects All with the keyboard and toggles it off (dynamic: %s)', + async (dynamicAll) => { + vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true) + const originalScroll = HTMLElement.prototype.scrollIntoView + HTMLElement.prototype.scrollIntoView = vi.fn() + mocks.loadAll.mockResolvedValue({ + status: 'complete', + options: [ { id: 'ENG', label: 'Engineering' }, { id: 'OPS', label: 'Operations' }, - ] - ) - await act(async () => - document - .querySelector('[role="option"]') - ?.dispatchEvent(new MouseEvent('mousedown', { bubbles: true })) - ) - expect(mocks.change).toHaveBeenLastCalledWith([], []) - expect(document.querySelector('[role="option"]')?.getAttribute('aria-selected')).toBe('false') - expect(mocks.loadAll).toHaveBeenCalledOnce() - } finally { - await act(async () => root.unmount()) - container.remove() - HTMLElement.prototype.scrollIntoView = originalScroll - vi.unstubAllGlobals() + ], + }) + const container = document.createElement('div') + document.body.appendChild(container) + const root = createRoot(container) + try { + await act(async () => root.render()) + expect(container.textContent).not.toContain('Select all') + expect(container.textContent).not.toContain('Clear') + const trigger = container.querySelector('[role="combobox"]') + expect(trigger).not.toBeNull() + await act(async () => + trigger?.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true })) + ) + expect(document.querySelector('[role="option"]')?.textContent).toBe('All') + await act(async () => + trigger?.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true })) + ) + expect(document.querySelector('[role="option"]')?.getAttribute('aria-selected')).toBe('true') + expect(mocks.loadAll).toHaveBeenCalledTimes(dynamicAll ? 0 : 1) + expect(mocks.change).toHaveBeenCalledWith( + dynamicAll ? ['*'] : ['ENG', 'OPS'], + dynamicAll + ? [{ id: '*', label: 'All' }] + : [ + { id: 'ENG', label: 'Engineering' }, + { id: 'OPS', label: 'Operations' }, + ] + ) + if (dynamicAll) expect(trigger?.textContent).toContain('All') + await act(async () => + document + .querySelector('[role="option"]') + ?.dispatchEvent(new MouseEvent('mousedown', { bubbles: true })) + ) + expect(mocks.change).toHaveBeenLastCalledWith([], []) + expect(document.querySelector('[role="option"]')?.getAttribute('aria-selected')).toBe('false') + expect(mocks.loadAll).toHaveBeenCalledTimes(dynamicAll ? 0 : 1) + } finally { + await act(async () => root.unmount()) + container.remove() + HTMLElement.prototype.scrollIntoView = originalScroll + vi.unstubAllGlobals() + } } -}) +) it.each(['empty', 'error'] as const)( 'shows the initial %s state without All and allows failed lists to retry', diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/hooks/use-connector-config-fields.test.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/hooks/use-connector-config-fields.test.tsx index fef39a83418..98e1067296b 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/hooks/use-connector-config-fields.test.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/hooks/use-connector-config-fields.test.tsx @@ -311,6 +311,40 @@ describe('useConnectorConfigFields member configuration', () => { } ) + it.each([ + [jiraConnectorMeta, 'projectKey', 'projectSelector'], + [confluenceConnectorMeta, 'spaceKey', 'spaceSelector'], + ] as const)( + 'preserves saved $0.name All scope through manual entry and clears it when the site changes', + (meta, canonicalId, selectorId) => { + render({ + connectorConfig: meta, + accessMode: 'members', + initialSourceConfig: { domain: 'team.atlassian.net', [selectorId]: ['*'] }, + initialSelectionLabels: { [canonicalId]: [{ id: '*', label: 'All' }] }, + }) + expect(current.resolveSourceConfig()[canonicalId]).toEqual(['*']) + act(() => current.toggleCanonicalMode(canonicalId)) + expect(current.sourceConfig[canonicalId]).toEqual(['*']) + expect(current.resolveSourceConfig()[canonicalId]).toEqual(['*']) + act(() => current.toggleCanonicalMode(canonicalId)) + expect(current.sourceConfig[selectorId]).toEqual(['*']) + expect(describeSearchSource(meta, current.resolveSourceConfig())).toBe( + 'team.atlassian.net · All' + ) + act(() => current.toggleCanonicalMode(canonicalId)) + act(() => current.handleFieldChange(canonicalId, 'ENG, PRODUCT')) + act(() => current.toggleCanonicalMode(canonicalId)) + expect(current.resolveSourceConfig()[canonicalId]).toEqual(['ENG', 'PRODUCT']) + act(() => current.handleFieldChange(selectorId, ['*'], [{ id: '*', label: 'All' }])) + act(() => current.handleFieldChange('domain', 'other.atlassian.net')) + expect(current.resolveSourceConfig()[canonicalId]).toEqual([]) + act(() => current.toggleCanonicalMode(canonicalId)) + expect(current.resolveSourceConfig()[canonicalId]).toEqual([]) + expect(current.selectionLabels).toEqual({}) + } + ) + it('clears dependent selector labels together with their values', () => { const meta: ConnectorMeta = { ...googleDriveConnectorMeta, diff --git a/apps/sim/connectors/all-selection.test.ts b/apps/sim/connectors/all-selection.test.ts new file mode 100644 index 00000000000..60711647332 --- /dev/null +++ b/apps/sim/connectors/all-selection.test.ts @@ -0,0 +1,158 @@ +/** @vitest-environment node */ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { clearAtlassianCloudIdCache } from '@/lib/atlassian/discovery' +import { confluenceConnector } from '@/connectors/confluence/confluence' +import { jiraConnector } from '@/connectors/jira/jira' + +const DOMAIN = 'all-validation.atlassian.net' +const CLOUD_ID = 'all-validation-cloud' +const fetchMock = vi.fn() + +beforeEach(() => { + fetchMock.mockReset() + clearAtlassianCloudIdCache() + vi.stubGlobal('fetch', fetchMock) +}) + +afterEach(() => vi.unstubAllGlobals()) + +describe('dynamic All source validation', () => { + describe.each([ + { name: 'Jira', connector: jiraConnector, field: 'projectKey' }, + { name: 'Confluence', connector: confluenceConnector, field: 'spaceKey' }, + ])('$name mixed All selection', ({ connector, field }) => { + const error = 'Use "*" by itself for All, or remove it to select individual items.' + + it.each([{ value: '*, ENG' }, { value: ['*', 'ENG'] }, { value: ['ENG', ' * '] }])( + 'rejects mixed keys before validation requests (%j)', + async ({ value }) => { + await expect( + connector.validateConfig('token', { domain: DOMAIN, [field]: value }) + ).resolves.toEqual({ valid: false, error }) + expect(fetchMock).not.toHaveBeenCalled() + } + ) + + it.each([false, true])( + 'rejects mixed keys before listing or splitting the scope (per member: %s)', + async (perMemberListing) => { + await expect( + connector.listDocuments('token', { domain: DOMAIN, [field]: ['*', 'ENG'] }, undefined, { + perMemberListing, + }) + ).rejects.toThrow(error) + expect(fetchMock).not.toHaveBeenCalled() + } + ) + + it('rejects mixed keys during resumed hydration before reading the provider', async () => { + await expect( + connector.getDocument('token', { domain: DOMAIN, [field]: 'ENG, *' }, 'document-1') + ).rejects.toThrow(error) + expect(fetchMock).not.toHaveBeenCalled() + }) + }) + + it.each([{ value: '*' }, { value: ['*'] }])( + 'validates Confluence All without enumerating or submitting literal space keys (%j)', + async ({ value: spaceKey }) => { + fetchMock.mockResolvedValueOnce( + Response.json({ + results: [{ id: 'space-1', key: 'ENG' }], + _links: { next: '?cursor=more-spaces' }, + }) + ) + await expect( + confluenceConnector.validateConfig( + 'token', + { domain: DOMAIN, spaceKey }, + { cloudId: CLOUD_ID, credentialDomain: DOMAIN } + ) + ).resolves.toEqual({ valid: true }) + expect(fetchMock).toHaveBeenCalledOnce() + const [input, options] = fetchMock.mock.calls[0] + const url = new URL(String(input)) + expect(url.pathname).toBe(`/ex/confluence/${CLOUD_ID}/wiki/api/v2/spaces`) + expect(url.searchParams.get('limit')).toBe('1') + expect(url.searchParams.has('keys')).toBe(false) + expect(options?.headers).toMatchObject({ Authorization: 'Bearer token' }) + } + ) + + it('does not let Confluence All bypass the service-account site binding', async () => { + await expect( + confluenceConnector.validateConfig( + 'token', + { domain: DOMAIN, spaceKey: ['*'] }, + { cloudId: CLOUD_ID, credentialDomain: 'other.atlassian.net' } + ) + ).resolves.toMatchObject({ valid: false, error: expect.stringContaining('must match') }) + expect(fetchMock).not.toHaveBeenCalled() + }) + + it('does not accept Confluence All when the provider denies space discovery', async () => { + fetchMock.mockResolvedValueOnce(Response.json({}, { status: 403 })) + await expect( + confluenceConnector.validateConfig( + 'token', + { domain: DOMAIN, spaceKey: '*' }, + { cloudId: CLOUD_ID } + ) + ).resolves.toMatchObject({ valid: false, error: expect.stringContaining('403') }) + }) + + it.each([{ value: '*' }, { value: ['*'] }])( + 'validates Jira All and its optional filter under the supplied account (%j)', + async ({ value: projectKey }) => { + fetchMock.mockImplementation(async () => Response.json({ issues: [] })) + await expect( + jiraConnector.validateConfig( + 'token', + { domain: DOMAIN, projectKey, jql: 'status = "Done"' }, + { cloudId: CLOUD_ID } + ) + ).resolves.toEqual({ valid: true }) + expect(fetchMock).toHaveBeenCalledTimes(2) + expect( + fetchMock.mock.calls.map(([input]) => new URL(String(input)).searchParams.get('jql')) + ).toEqual(['project IS NOT EMPTY', 'project IS NOT EMPTY AND (status = "Done")']) + for (const [input, options] of fetchMock.mock.calls) { + const url = new URL(String(input)) + expect(url.pathname).toBe(`/ex/jira/${CLOUD_ID}/rest/api/3/search/jql`) + expect(url.searchParams.get('maxResults')).toBe('1') + expect(options?.headers).toMatchObject({ Authorization: 'Bearer token' }) + } + } + ) + + it('does not let Jira All bypass current account site discovery', async () => { + fetchMock.mockResolvedValueOnce( + Response.json([{ id: 'other-cloud', url: 'https://other.atlassian.net' }]) + ) + await expect( + jiraConnector.validateConfig( + 'token', + { domain: DOMAIN, projectKey: ['*'] }, + { perMemberListing: true } + ) + ).resolves.toMatchObject({ + valid: false, + error: expect.stringContaining('Could not match Jira domain'), + }) + expect(fetchMock).toHaveBeenCalledOnce() + expect(String(fetchMock.mock.calls[0][0])).toBe( + 'https://api.atlassian.com/oauth/token/accessible-resources' + ) + }) + + it('does not accept Jira All when the provider denies issue search', async () => { + fetchMock.mockResolvedValueOnce(Response.json({}, { status: 403 })) + await expect( + jiraConnector.validateConfig( + 'token', + { domain: DOMAIN, projectKey: '*' }, + { cloudId: CLOUD_ID } + ) + ).resolves.toMatchObject({ valid: false, error: expect.stringContaining('403') }) + }) +}) diff --git a/apps/sim/connectors/confluence/attachments.test.ts b/apps/sim/connectors/confluence/attachments.test.ts new file mode 100644 index 00000000000..f74e033e96c --- /dev/null +++ b/apps/sim/connectors/confluence/attachments.test.ts @@ -0,0 +1,615 @@ +/** + * @vitest-environment node + */ +import JSZip from 'jszip' +import { PDFDocument, StandardFonts } from 'pdf-lib' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { DEFAULT_MAX_ERROR_BODY_BYTES, PayloadSizeLimitError } from '@/lib/core/utils/stream-limits' +import { parseBuffer } from '@/lib/file-parsers' +import { listConfluenceAttachments } from '@/connectors/confluence/attachments' +import { confluenceConnector } from '@/connectors/confluence/confluence' +import type { ExternalDocument, ExternalDocumentList } from '@/connectors/types' +import { CONNECTOR_MAX_FILE_BYTES } from '@/connectors/utils' + +const { secureDownload } = vi.hoisted(() => ({ secureDownload: vi.fn() })) +vi.mock('@/lib/knowledge/documents/secure-fetch.server', async (importOriginal) => ({ + ...(await importOriginal()), + secureFetchWithRetry: secureDownload, +})) + +const CONFIG = { domain: 'example.atlassian.net', spaceKey: 'ENG' } +const CONTEXT = { cloudId: 'cloud', spaceId: '1' } +const INPUT = { accessToken: 'token', cloudId: 'cloud', domain: CONFIG.domain } +const fetchMock = vi.fn() + +function file(overrides: Record = {}) { + return { + id: 'att123', + title: 'Guide.pdf', + status: 'current', + pageId: 'p1', + fileSize: 12, + version: { number: 2, createdAt: '2026-09-01T00:00:00Z' }, + webuiLink: '/spaces/ENG/pages/p1?preview=att123', + ...overrides, + } +} + +function parent(id = 'p1', type = 'page'): ExternalDocument { + return { + externalId: id, + title: id, + content: '', + mimeType: 'text/plain', + contentHash: id, + metadata: { contentType: type }, + } +} + +function fixture(attachment = file()) { + fetchMock.mockImplementation(async (input) => { + const url = new URL(String(input)) + const path = url.pathname + if (path.endsWith('/attachments/att123')) return Response.json(attachment) + if (path.endsWith('/pages/p1/attachments')) return Response.json({ results: [attachment] }) + if (path.endsWith('/spaces/1/pages')) + return Response.json({ + results: [ + { id: 'p1', title: 'Parent', status: 'current', spaceId: '1', version: { number: 1 } }, + ], + }) + if (path.endsWith('/pages/p1')) + return Response.json({ id: 'p1', status: 'current', spaceId: '1' }) + if (path.endsWith('/spaces/1')) return Response.json({ key: 'ENG' }) + if (path.endsWith('/pages/p1/labels')) + return Response.json({ results: [{ name: 'published' }] }) + if (path.endsWith('/download')) + return new Response(null, { + status: 302, + headers: { location: 'https://files.atlassian.net/signed-file?token=secret' }, + }) + if (path.endsWith('/spaces/1/permissions')) + return Response.json({ + results: [ + { + principal: { type: 'user', id: 'reader' }, + operation: { key: 'read', targetType: 'space' }, + }, + ], + }) + if (path.endsWith('/restriction/byOperation/read')) + return Response.json({ + restrictions: { user: { results: [] }, group: { results: [{ id: 'parent-readers' }] } }, + }) + if (path.endsWith('/ancestors')) return Response.json({ results: [] }) + throw new Error(`Unexpected request: ${url}`) + }) +} + +async function get(externalId = 'attachment:page:p1:att123', config = CONFIG) { + return confluenceConnector.getDocument('token', config, externalId, { ...CONTEXT }) +} + +beforeEach(() => { + fetchMock.mockReset() + secureDownload.mockReset().mockResolvedValue(new Response('binary bytes')) + vi.stubGlobal('fetch', fetchMock) +}) +afterEach(() => vi.unstubAllGlobals()) + +describe('Confluence attachment listing', () => { + it('lists PDF, DOC and DOCX stubs without downloading, and excludes unsupported or archived files', async () => { + fetchMock.mockResolvedValue( + Response.json({ + results: [ + file(), + file({ id: '2', title: 'Legacy.DOC' }), + file({ id: '3', title: 'Modern.docx' }), + file({ id: '4', title: 'image.png' }), + file({ id: '5', title: 'old.pdf', status: 'archived' }), + ], + }) + ) + const result = await listConfluenceAttachments({ + ...INPUT, + listParents: async () => ({ documents: [parent()], hasMore: false }), + }) + expect(result.documents.map((doc) => doc.externalId)).toEqual([ + 'p1', + 'attachment:page:p1:att123', + 'attachment:page:p1:2', + 'attachment:page:p1:3', + ]) + expect( + result.documents.slice(1).every((doc) => doc.contentDeferred && doc.content === '') + ).toBe(true) + expect(result.hasMore).toBe(false) + expect(secureDownload).not.toHaveBeenCalled() + expect(new URL(String(fetchMock.mock.calls[0][0])).searchParams.get('status')).toBe('current') + }) + + it('resumes a bounded parent queue with a fresh runtime context', async () => { + fetchMock.mockImplementation(async () => Response.json({ results: [] })) + const listParents = vi.fn( + async (): Promise => ({ + documents: Array.from({ length: 8 }, (_, index) => parent(`p${index}`)), + hasMore: false, + }) + ) + const first = await listConfluenceAttachments({ ...INPUT, listParents }) + expect(first.documents).toHaveLength(8) + expect(fetchMock).toHaveBeenCalledTimes(5) + expect(first.hasMore).toBe(true) + const last = await listConfluenceAttachments({ + ...INPUT, + listParents, + cursor: first.nextCursor, + syncContext: { totalDocsFetched: 8000 }, + }) + expect(last.hasMore).toBe(false) + expect(fetchMock).toHaveBeenCalledTimes(8) + expect(listParents).toHaveBeenCalledTimes(1) + }) + + it('keeps parent caps independent of attachment counts across worker resumes', async () => { + fetchMock.mockImplementation(async () => + Response.json({ + results: Array.from({ length: 10 }, (_, index) => file({ id: String(index) })), + }) + ) + const listParents = vi.fn( + async ( + cursor: string | undefined, + context: Record + ): Promise => { + expect(context.totalDocsFetched).toBe(cursor ? 1 : 0) + return { + documents: [parent()], + hasMore: !cursor, + nextCursor: cursor ? undefined : 'next-parent', + } + } + ) + const first = await listConfluenceAttachments({ ...INPUT, listParents }) + expect(first.documents).toHaveLength(11) + const last = await listConfluenceAttachments({ + ...INPUT, + listParents, + cursor: first.nextCursor, + syncContext: { totalDocsFetched: 11 }, + }) + expect(last.hasMore).toBe(false) + expect(listParents).toHaveBeenCalledTimes(2) + }) + + it('follows attachment continuations beyond the per-call request budget', async () => { + fetchMock.mockImplementation(async (input) => { + const cursor = Number(new URL(String(input)).searchParams.get('cursor') || 0) + return Response.json({ + results: [file({ id: String(cursor) })], + _links: cursor < 6 ? { next: `?cursor=${cursor + 1}` } : {}, + }) + }) + const listParents = vi.fn( + async (): Promise => ({ documents: [parent()], hasMore: false }) + ) + const first = await listConfluenceAttachments({ ...INPUT, listParents }) + const last = await listConfluenceAttachments({ + ...INPUT, + listParents, + cursor: first.nextCursor, + }) + expect([...first.documents, ...last.documents].map((doc) => doc.externalId)).toEqual([ + 'p1', + ...Array.from({ length: 7 }, (_, i) => `attachment:page:p1:${i}`), + ]) + expect(last.hasMore).toBe(false) + }) + + it.each([403, 401])( + 'preserves parent pages and cumulative partial progress for attachment access failure %s', + async (status) => { + fetchMock.mockImplementation(async () => + status === 401 + ? Response.json({ code: 401, message: 'Unauthorized; scope does not match' }, { status }) + : new Response(null, { status }) + ) + const listParents = vi.fn( + async (cursor: string | undefined): Promise => ({ + documents: [parent(cursor ? 'p2' : 'p1')], + hasMore: !cursor, + nextCursor: cursor ? undefined : 'next', + }) + ) + const first = await listConfluenceAttachments({ ...INPUT, listParents }) + const context: Record = {} + const last = await listConfluenceAttachments({ + ...INPUT, + listParents, + cursor: first.nextCursor, + syncContext: context, + }) + expect(first.documents.map((doc) => doc.externalId)).toEqual(['p1']) + expect(last.documents.map((doc) => doc.externalId)).toEqual(['p2']) + expect(last.listingFailures).toEqual({ + count: 2, + samples: ['p1', 'p2'].map((scope) => ({ + scope, + operation: 'confluence.attachments.list', + status, + reasons: [status === 401 ? 'attachment_scope_mismatch' : 'attachment_access_denied'], + })), + }) + expect(last.reconciliationSafe).toBe(false) + expect(context.reconciliationUnsafe).toBe(true) + } + ) + + it('continues accepting the parent cursor of a listing interrupted before attachments shipped', async () => { + const listParents = vi.fn( + async (): Promise => ({ documents: [], hasMore: false }) + ) + await listConfluenceAttachments({ + ...INPUT, + listParents, + cursor: 'space-batches:{"batch":1,"cursor":"provider"}', + syncContext: { totalDocsFetched: 27 }, + }) + expect(listParents).toHaveBeenCalledWith( + 'space-batches:{"batch":1,"cursor":"provider"}', + expect.objectContaining({ totalDocsFetched: 27 }) + ) + }) + + it('lists attachments even when only their version changed after the parent page watermark', async () => { + fixture() + const result = await confluenceConnector.listDocuments( + 'token', + CONFIG, + undefined, + { ...CONTEXT }, + new Date('2026-09-15') + ) + expect(result.documents.some((doc) => doc.externalId === 'attachment:page:p1:att123')).toBe( + true + ) + expect(fetchMock.mock.calls.some(([url]) => String(url).includes('lastModified'))).toBe(false) + }) + + it('surfaces known oversized files as skipped without downloading', async () => { + fetchMock.mockResolvedValue( + Response.json({ results: [file({ fileSize: CONNECTOR_MAX_FILE_BYTES + 1 })] }) + ) + const result = await listConfluenceAttachments({ + ...INPUT, + listParents: async () => ({ documents: [parent()], hasMore: false }), + }) + expect(result.documents[1].skippedReason).toContain('size limit') + expect(result.documents[1].contentDeferred).toBe(false) + }) + + it.each([ + { results: [], _links: { next: '?wrong=cursor' } }, + { results: [file({ pageId: 'another-parent' })] }, + { results: 'invalid' }, + ])('refuses an incomplete or mismatched attachment list %#', async (body) => { + fetchMock.mockResolvedValue(Response.json(body)) + await expect( + listConfluenceAttachments({ + ...INPUT, + listParents: async () => ({ documents: [parent()], hasMore: false }), + }) + ).rejects.toThrow() + }) + + it.each([ + null, + '{"code":401,"message":"Token is invalid","secret":"must-not-appear"}', + '{"code":403,"message":"Unauthorized; scope does not match"}', + 'invalid JSON', + 'x'.repeat(DEFAULT_MAX_ERROR_BODY_BYTES + 1), + ])('keeps unrecognized 401 responses as credential failures %#', async (body) => { + fetchMock.mockResolvedValue(new Response(body, { status: 401 })) + const error = await listConfluenceAttachments({ + ...INPUT, + listParents: async () => ({ documents: [parent()], hasMore: false }), + }).catch((error: unknown) => error) + expect(error).toMatchObject({ status: 401 }) + expect(confluenceConnector.isCredentialInvalidError?.(error)).toBe(true) + expect(String(error)).not.toContain('must-not-appear') + }) +}) + +describe('Confluence attachment hydration', () => { + it.each(['/attachments/att123', '/download'])( + 'reports missing attachment scope without invalidating the credential at %s', + async (endpoint) => { + fixture() + const healthy = fetchMock.getMockImplementation()! + fetchMock.mockImplementation(async (input, init) => + new URL(String(input)).pathname.endsWith(endpoint) + ? Response.json( + { + code: 401, + message: 'Unauthorized; scope does not match', + secret: 'must-not-appear', + }, + { status: 401 } + ) + : healthy(input, init) + ) + const error = await get().catch((error: unknown) => error) + expect(error).toBeInstanceOf(Error) + expect(String(error)).toContain('read:attachment:confluence') + expect(String(error)).not.toContain('must-not-appear') + expect(confluenceConnector.isCredentialInvalidError?.(error)).toBe(false) + expect(secureDownload).not.toHaveBeenCalled() + } + ) + + it.each(['/attachments/att123', '/download'])( + 'preserves expired-credential errors during hydration at %s', + async (endpoint) => { + fixture() + const healthy = fetchMock.getMockImplementation()! + fetchMock.mockImplementation(async (input, init) => + new URL(String(input)).pathname.endsWith(endpoint) + ? new Response(null, { status: 401 }) + : healthy(input, init) + ) + const error = await get().catch((error: unknown) => error) + expect(error).toMatchObject({ status: 401 }) + expect(confluenceConnector.isCredentialInvalidError?.(error)).toBe(true) + expect(secureDownload).not.toHaveBeenCalled() + } + ) + + it.each(['pdf', 'doc', 'docx'])( + 'hands an original %s file to the shared parser pipeline', + async (extension) => { + fixture(file({ title: `Guide.${extension}` })) + const listing = await confluenceConnector.listDocuments('token', CONFIG, undefined, { + ...CONTEXT, + }) + const doc = await get() + expect(doc?.sourceFile?.bytes.toString()).toBe('binary bytes') + expect(doc?.sourceFile?.fileName).toBe(`Guide.${extension}`) + expect(doc?.contentHash).toBe(listing.documents[1].contentHash) + expect(doc?.contentDeferred).toBe(false) + expect(doc?.mimeType).toBe(doc?.sourceFile?.mimeType) + const download = fetchMock.mock.calls.find(([url]) => String(url).includes('/download'))! + expect(String(download[0])).toContain( + '/content/p1/child/attachment/att123/download?version=2' + ) + expect(download[1]).toMatchObject({ + redirect: 'manual', + headers: { Authorization: 'Bearer token' }, + }) + expect(secureDownload.mock.calls[0][1]).toMatchObject({ + profile: 'contentFetch', + maxResponseBytes: CONNECTOR_MAX_FILE_BYTES, + }) + expect(secureDownload.mock.calls[0][1].headers).toBeUndefined() + } + ) + + it.each([ + '/spaces/ENG/pages/p1?preview=att123', + '/wiki/spaces/ENG/pages/p1?preview=att123', + 'https://example.atlassian.net/wiki/spaces/ENG/pages/p1?preview=att123', + ])('normalizes provider web links %s', async (webuiLink) => { + fixture(file({ webuiLink })) + expect((await get())?.sourceUrl).toBe( + 'https://example.atlassian.net/wiki/spaces/ENG/pages/p1?preview=att123' + ) + }) + + it('refuses a file moved after listing even when the token can read the new parent', async () => { + fixture(file({ pageId: 'private-page' })) + expect(await get()).toBeNull() + expect(secureDownload).not.toHaveBeenCalled() + expect(fetchMock).toHaveBeenCalledTimes(1) + }) + + it('refuses a current parent moved out of the selected space', async () => { + fixture() + const original = fetchMock.getMockImplementation()! + fetchMock.mockImplementation(async (input, init) => + String(input).endsWith('/spaces/1') + ? Response.json({ key: 'PRIVATE' }) + : original(input, init) + ) + expect(await get()).toBeNull() + expect(secureDownload).not.toHaveBeenCalled() + }) + + it('checks labels beyond the embedded fifty-label page before downloading', async () => { + fixture() + const original = fetchMock.getMockImplementation()! + fetchMock.mockImplementation(async (input, init) => { + const url = new URL(String(input)) + if (url.pathname.endsWith('/labels')) + return Response.json( + url.searchParams.has('cursor') + ? { results: [{ name: 'published' }] } + : { results: [{ name: 'other' }], _links: { next: '?cursor=second' } } + ) + return original(input, init) + }) + expect( + (await get(undefined, { ...CONFIG, labelFilter: 'published' } as typeof CONFIG))?.sourceFile + ).toBeDefined() + expect(fetchMock.mock.calls.filter(([url]) => String(url).includes('/labels'))).toHaveLength(2) + }) + + it('does not download when the current parent no longer matches a label filter', async () => { + fixture() + expect(await get(undefined, { ...CONFIG, labelFilter: 'missing' } as typeof CONFIG)).toBeNull() + expect(secureDownload).not.toHaveBeenCalled() + }) + + it('rejects non-HTTPS redirects before any download and guards subsequent hops', async () => { + fixture() + const original = fetchMock.getMockImplementation()! + fetchMock.mockImplementation(async (input, init) => + String(input).includes('/download') + ? new Response(null, { status: 302, headers: { location: 'http://127.0.0.1/secret' } }) + : original(input, init) + ) + await expect(get()).rejects.toThrow('unsafe') + expect(secureDownload).not.toHaveBeenCalled() + fixture() + await get() + expect(() => + secureDownload.mock.calls[0][1].assertRedirectTarget('http://internal/file') + ).toThrow('unsafe') + }) + + it('surfaces an oversized streamed download as a visible skip', async () => { + fixture() + secureDownload.mockRejectedValue( + new PayloadSizeLimitError({ label: 'download', maxBytes: CONNECTOR_MAX_FILE_BYTES }) + ) + expect((await get())?.skippedReason).toContain('size limit') + }) + + it('releases a rejected signed download response', async () => { + fixture() + const cancel = vi.fn() + secureDownload.mockResolvedValue(new Response(new ReadableStream({ cancel }), { status: 403 })) + await expect(get()).rejects.toThrow('Failed to download Confluence attachment: 403') + expect(cancel).toHaveBeenCalledOnce() + }) + + it('passes cancellation through metadata and signed download requests', async () => { + fixture() + const signal = new AbortController().signal + await confluenceConnector.getDocument('token', CONFIG, 'attachment:page:p1:att123', { + ...CONTEXT, + signal, + }) + expect(fetchMock.mock.calls.every(([, init]) => init?.signal instanceof AbortSignal)).toBe(true) + expect(secureDownload.mock.calls[0][1].signal).toBe(signal) + }) + + it.each(['pdf', 'docx'] as const)( + 'roundtrips genuine %s bytes through the public parser', + async (extension) => { + let bytes: Buffer + if (extension === 'pdf') { + const document = await PDFDocument.create() + const font = await document.embedFont(StandardFonts.Helvetica) + document.addPage().drawText('Confluence attachment text', { font, size: 14 }) + bytes = Buffer.from(await document.save()) + } else { + const zip = new JSZip() + zip.file( + '[Content_Types].xml', + '' + ) + zip.file( + '_rels/.rels', + '' + ) + zip.file( + 'word/document.xml', + 'Confluence attachment text' + ) + bytes = await zip.generateAsync({ type: 'nodebuffer' }) + } + fixture(file({ title: `Guide.${extension}`, fileSize: bytes.length })) + secureDownload.mockResolvedValue(new Response(bytes)) + const doc = await get() + expect(doc?.sourceFile).toBeDefined() + const parsed = await parseBuffer(doc!.sourceFile!.bytes, extension) + expect(parsed.content).toContain('Confluence attachment text') + } + ) +}) + +describe('Confluence attachment ACLs', () => { + it('hydrates blog post attachments and checks blog restrictions without page ancestors', async () => { + fixture(file({ pageId: undefined, blogPostId: 'b1' })) + const original = fetchMock.getMockImplementation()! + fetchMock.mockImplementation(async (input, init) => { + const path = new URL(String(input)).pathname + if (path.endsWith('/blogposts/b1/attachments')) + return Response.json({ results: [file({ pageId: undefined, blogPostId: 'b1' })] }) + if (path.endsWith('/blogposts/b1')) + return Response.json({ id: 'b1', spaceId: '1', status: 'current' }) + return original(input, init) + }) + const listing = await listConfluenceAttachments({ + ...INPUT, + listParents: async () => ({ documents: [parent('b1', 'blogpost')], hasMore: false }), + }) + const config = { ...CONFIG, contentType: 'blogpost' } + const doc = listing.documents[1] + const hydrated = await confluenceConnector.getDocument('token', config, doc.externalId, { + ...CONTEXT, + }) + expect(hydrated?.sourceFile).toBeDefined() + const acls = await confluenceConnector.getDocumentAcls!( + 'token', + config, + [doc], + { ...CONTEXT }, + { persistGroupMembership: vi.fn() } + ) + expect(acls[doc.externalId]).toEqual({ + acl: ['g:confluence:cloud:space-readers:1'], + requirements: [['g:confluence:cloud:parent-readers']], + }) + expect( + fetchMock.mock.calls.some(([url]) => String(url).includes('/content/b1/restriction')) + ).toBe(true) + expect(fetchMock.mock.calls.some(([url]) => String(url).includes('/ancestors'))).toBe(false) + }) + + it('uses the canonical parent restriction chain and persists its space audience', async () => { + fixture() + const listed = await confluenceConnector.listDocuments('token', CONFIG, undefined, { + ...CONTEXT, + }) + const persistGroupMembership = vi.fn().mockResolvedValue(undefined) + const acls = await confluenceConnector.getDocumentAcls!( + 'token', + CONFIG, + [listed.documents[1]], + { ...CONTEXT }, + { persistGroupMembership } + ) + expect(acls['attachment:page:p1:att123']).toEqual({ + acl: ['g:confluence:cloud:space-readers:1'], + requirements: [['g:confluence:cloud:parent-readers']], + }) + expect(persistGroupMembership).toHaveBeenCalledOnce() + expect( + fetchMock.mock.calls.some(([url]) => String(url).includes('/content/att123/restriction')) + ).toBe(false) + expect( + fetchMock.mock.calls.some(([url]) => String(url).includes('/pages/att123/ancestors')) + ).toBe(false) + }) + + it('fails closed for a moved attachment and for missing audience persistence', async () => { + fixture() + const listed = await confluenceConnector.listDocuments('token', CONFIG, undefined, { + ...CONTEXT, + }) + const doc = listed.documents[1] + expect( + await confluenceConnector.getDocumentAcls!('token', CONFIG, [doc], { ...CONTEXT }) + ).toEqual({}) + fixture(file({ pageId: 'new-parent' })) + expect( + await confluenceConnector.getDocumentAcls!( + 'token', + CONFIG, + [doc], + { ...CONTEXT }, + { persistGroupMembership: vi.fn() } + ) + ).toEqual({}) + }) +}) diff --git a/apps/sim/connectors/confluence/attachments.ts b/apps/sim/connectors/confluence/attachments.ts new file mode 100644 index 00000000000..087e84faa20 --- /dev/null +++ b/apps/sim/connectors/confluence/attachments.ts @@ -0,0 +1,478 @@ +import { z } from 'zod' +import { isPayloadSizeLimitError, readResponseJsonWithLimit } from '@/lib/core/utils/stream-limits' +import { fetchWithRetry, secureFetchWithRetry } from '@/lib/knowledge/documents/secure-fetch.server' +import { + createRetryableHttpError, + readBoundedHttpErrorPayload, +} from '@/lib/knowledge/documents/utils' +import { extractCursor } from '@/connectors/confluence/cursor' +import { listingFailuresSchema, MAX_LISTING_FAILURE_SAMPLES } from '@/connectors/listing-failures' +import { isAllSourceItems } from '@/connectors/selection' +import type { ExternalDocument, ExternalDocumentList } from '@/connectors/types' +import { + CONNECTOR_MAX_FILE_BYTES, + connectorFileExtension, + markSkipped, + parseMultiValue, + pipelineParsedMimeType, + readBodyWithLimit, + sizeLimitSkipReason, + stubOrSkipBySize, +} from '@/connectors/utils' + +const ATTACHMENT_PREFIX = 'attachment:' +const CURSOR_PREFIX = 'attachments:' +const PAGE_SIZE = 50 +const REQUESTS_PER_CALL = 5 +const MAX_CURSOR_BYTES = 512 * 1024 +const MAX_METADATA_BYTES = 2 * 1024 * 1024 +const FILE_EXTENSIONS = new Set(['pdf', 'doc', 'docx']) +const boundedId = z.string().min(1).max(254) +const providerCursor = z.string().min(1).max(8192) +const parentSchema = z.object({ id: boundedId, type: z.enum(['page', 'blogpost']) }) +const cursorSchema = z.object({ + parentCursor: z + .string() + .min(1) + .max(32 * 1024) + .optional(), + parents: z.array(parentSchema).max(500), + attachmentCursor: providerCursor.optional(), + parentsListed: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER), + parentsDone: z.boolean(), + failures: listingFailuresSchema.optional(), +}) +const attachmentSchema = z.object({ + id: boundedId, + title: z.string().min(1).max(4096), + status: z.string(), + pageId: boundedId.optional(), + blogPostId: boundedId.optional(), + customContentId: boundedId.optional(), + fileSize: z.number().int().nonnegative().optional(), + version: z.object({ number: z.number().int().positive(), createdAt: z.string().optional() }), + webuiLink: z.string().optional(), + _links: z.object({ webui: z.string().optional() }).optional(), +}) +const attachmentPageSchema = z.object({ + results: z.array(attachmentSchema).max(250), + _links: z.object({ next: providerCursor.optional() }).optional(), +}) +const scopeMismatchSchema = z.object({ + code: z.literal(401), + message: z.literal('Unauthorized; scope does not match'), +}) + +type Attachment = z.infer +type AttachmentCursor = z.infer +type AttachmentParent = z.infer + +interface AttachmentRequest { + accessToken: string + cloudId: string + domain: string + syncContext?: Record +} + +class ConfluenceAttachmentScopeError extends Error { + constructor() { + super('Confluence attachments require the read:attachment:confluence credential scope.') + this.name = 'ConfluenceAttachmentScopeError' + } +} + +/** Atlassian reports missing endpoint scopes as 401 without invalidating the token itself. */ +async function unauthorizedAttachmentError(response: Response): Promise { + const payload = await readBoundedHttpErrorPayload(response) + if (payload.ok) { + try { + if (scopeMismatchSchema.safeParse(JSON.parse(payload.body)).success) { + return new ConfluenceAttachmentScopeError() + } + } catch { + /** An unrecognized response remains an authentication failure. */ + } + } + return createRetryableHttpError({ status: response.status, headers: response.headers }) +} + +function signalFor(input: AttachmentRequest): AbortSignal | undefined { + const signal = input.syncContext?.signal + return signal instanceof AbortSignal ? signal : undefined +} + +function apiBase(cloudId: string): string { + return `https://api.atlassian.com/ex/confluence/${encodeURIComponent(cloudId)}/wiki` +} + +async function requestMetadata(input: AttachmentRequest, path: string): Promise { + return fetchWithRetry(`${apiBase(input.cloudId)}${path}`, { + headers: { Authorization: `Bearer ${input.accessToken}`, Accept: 'application/json' }, + signal: signalFor(input), + redirect: 'error', + }) +} + +async function readMetadata(response: Response): Promise { + if (!response.ok) { + if (response.status === 401) throw await unauthorizedAttachmentError(response) + await response.body?.cancel() + throw new Error( + response.status === 403 + ? 'Confluence attachment access was denied. Check the parent content permissions and the read:attachment:confluence credential scope.' + : `Failed to read Confluence attachment metadata: ${response.status}` + ) + } + return readResponseJsonWithLimit(response, { + maxBytes: MAX_METADATA_BYTES, + label: 'Confluence attachment metadata', + }) +} + +function attachmentParent(attachment: Attachment): AttachmentParent { + if (attachment.customContentId || Boolean(attachment.pageId) === Boolean(attachment.blogPostId)) { + throw new Error('Confluence attachment has no unambiguous page or blog post parent') + } + return attachment.pageId + ? { id: attachment.pageId, type: 'page' } + : { id: attachment.blogPostId!, type: 'blogpost' } +} + +function sameParent(left: AttachmentParent, right: AttachmentParent): boolean { + return left.id === right.id && left.type === right.type +} + +function attachmentMimeType(attachment: Attachment): string | undefined { + const extension = connectorFileExtension(attachment.title) + return extension && FILE_EXTENSIONS.has(extension) + ? pipelineParsedMimeType(attachment.title) + : undefined +} + +function attachmentToStub( + attachment: Attachment, + parent: AttachmentParent, + domain: string +): ExternalDocument { + const fallback = `https://${domain}/wiki/pages/viewpage.action?pageId=${encodeURIComponent(parent.id)}` + const link = attachment.webuiLink ?? attachment._links?.webui + let sourceUrl = fallback + if (link) { + try { + const relative = link.startsWith('/') && !link.startsWith('/wiki/') ? `/wiki${link}` : link + const url = new URL(relative, `https://${domain}/wiki/`) + if (url.origin === `https://${domain}`) sourceUrl = url.toString() + } catch { + sourceUrl = fallback + } + } + return { + externalId: `${ATTACHMENT_PREFIX}${parent.type}:${encodeURIComponent(parent.id)}:${encodeURIComponent(attachment.id)}`, + title: attachment.title, + content: '', + contentDeferred: true, + mimeType: attachmentMimeType(attachment) ?? 'application/octet-stream', + contentHash: `confluence:attachment-v1:${attachment.id}:${parent.type}:${parent.id}:${attachment.version.number}`, + sourceUrl, + metadata: { + contentType: 'attachment', + parentId: parent.id, + parentContentType: parent.type, + version: attachment.version.number, + lastModified: attachment.version.createdAt, + fileSize: attachment.fileSize, + }, + } +} + +function decodeCursor(cursor: string | undefined, totalFetched: unknown): AttachmentCursor { + if (!cursor?.startsWith(CURSOR_PREFIX)) { + return { + parents: [], + parentCursor: cursor, + parentsDone: false, + parentsListed: typeof totalFetched === 'number' ? totalFetched : 0, + } + } + if (Buffer.byteLength(cursor, 'utf8') > MAX_CURSOR_BYTES) { + throw new Error('Confluence attachment continuation exceeds its size limit') + } + const parsed = cursorSchema.safeParse(JSON.parse(cursor.slice(CURSOR_PREFIX.length))) + if (!parsed.success || (parsed.data.attachmentCursor && parsed.data.parents.length === 0)) { + throw new Error('Invalid Confluence attachment continuation. Restart the sync.') + } + return parsed.data +} + +/** + * Visits attachment metadata beneath the already-filtered parents. The cursor + * contains only a bounded parent queue, never bodies or binary data. Parent caps + * remain independent of the sync engine's combined page-and-attachment count. + */ +export async function listConfluenceAttachments( + input: AttachmentRequest & { + cursor?: string + listParents: ( + cursor: string | undefined, + syncContext: Record + ) => Promise + } +): Promise { + const state = decodeCursor(input.cursor, input.syncContext?.totalDocsFetched) + const documents: ExternalDocument[] = [] + if (state.parents.length === 0 && !state.parentsDone) { + const parentContext = { ...input.syncContext, totalDocsFetched: state.parentsListed } + const page = await input.listParents(state.parentCursor, parentContext) + if (page.documents.length > 500 || (page.hasMore && !page.nextCursor)) { + throw new Error('Confluence returned an invalid parent listing') + } + if (input.syncContext) Object.assign(input.syncContext, parentContext) + state.parentsListed += page.documents.length + state.parentCursor = page.nextCursor + state.parentsDone = !page.hasMore + state.parents = page.documents.map((doc) => + parentSchema.parse({ id: doc.externalId, type: doc.metadata?.contentType ?? 'page' }) + ) + documents.push(...page.documents) + } + + for (let request = 0; state.parents.length > 0 && request < REQUESTS_PER_CALL; request++) { + const parent = state.parents[0] + const query = new URLSearchParams({ limit: String(PAGE_SIZE), status: 'current' }) + if (state.attachmentCursor) query.set('cursor', state.attachmentCursor) + const response = await requestMetadata( + input, + `/api/v2/${parent.type}s/${encodeURIComponent(parent.id)}/attachments?${query}` + ) + let page: z.infer + try { + page = attachmentPageSchema.parse(await readMetadata(response)) + } catch (error) { + const missingScope = error instanceof ConfluenceAttachmentScopeError + if (!missingScope && response.status !== 403 && response.status !== 404) throw error + state.failures ??= { count: 0, samples: [] } + state.failures.count += 1 + if (state.failures.samples.length < MAX_LISTING_FAILURE_SAMPLES) { + state.failures.samples.push({ + scope: parent.id, + operation: 'confluence.attachments.list', + status: response.status, + reasons: [missingScope ? 'attachment_scope_mismatch' : 'attachment_access_denied'], + }) + } + state.parents.shift() + state.attachmentCursor = undefined + continue + } + for (const attachment of page.results) { + if (attachment.status !== 'current' || !attachmentMimeType(attachment)) continue + if (!sameParent(attachmentParent(attachment), parent)) { + throw new Error('Confluence returned an attachment belonging to another parent') + } + documents.push( + stubOrSkipBySize( + attachmentToStub(attachment, parent, input.domain), + attachment.fileSize, + CONNECTOR_MAX_FILE_BYTES + ) + ) + } + const next = page._links?.next + const nextCursor = extractCursor(next) + if (next && (!nextCursor || nextCursor === state.attachmentCursor)) { + throw new Error('Confluence returned an invalid or repeated attachment continuation') + } + state.attachmentCursor = nextCursor + if (!nextCursor) state.parents.shift() + } + if (state.failures && input.syncContext) input.syncContext.reconciliationUnsafe = true + const hasMore = state.parents.length > 0 || !state.parentsDone + const nextCursor = hasMore ? CURSOR_PREFIX + JSON.stringify(state) : undefined + if (nextCursor && Buffer.byteLength(nextCursor, 'utf8') > MAX_CURSOR_BYTES) { + throw new Error('Confluence attachment continuation exceeds its size limit') + } + return { + documents, + hasMore, + nextCursor, + ...(state.failures ? { listingFailures: state.failures, reconciliationSafe: false } : {}), + } +} + +export function isConfluenceAttachment(externalId: string): boolean { + return externalId.startsWith(ATTACHMENT_PREFIX) +} + +async function readAttachment( + input: AttachmentRequest, + externalId: string +): Promise { + const [type, parentId, attachmentId, extra] = externalId + .slice(ATTACHMENT_PREFIX.length) + .split(':') + if (extra !== undefined || !parentId || !attachmentId) + throw new Error('Invalid Confluence attachment identity') + const expectedParent = parentSchema.parse({ type, id: decodeURIComponent(parentId) }) + const id = boundedId.parse(decodeURIComponent(attachmentId)) + const response = await requestMetadata(input, `/api/v2/attachments/${encodeURIComponent(id)}`) + if (response.status === 404) { + await response.body?.cancel() + return null + } + const attachment = attachmentSchema.parse(await readMetadata(response)) + if (attachment.id !== id) throw new Error('Confluence returned another attachment') + if (attachment.status !== 'current' || attachment.customContentId) return null + return sameParent(attachmentParent(attachment), expectedParent) ? attachment : null +} + +/** Verifies the current parent's configured scope rather than trusting stored metadata. */ +async function parentLocation( + input: AttachmentRequest, + parent: AttachmentParent, + sourceConfig: Record +): Promise<{ spaceId: string; contentType: 'page' | 'blogpost' } | null> { + const selectedType = sourceConfig.contentType || 'page' + if (selectedType !== 'all' && selectedType !== parent.type) return null + const path = `/api/v2/${parent.type}s/${encodeURIComponent(parent.id)}` + const response = await requestMetadata(input, path) + if (response.status === 404) { + await response.body?.cancel() + return null + } + const page = z + .object({ id: boundedId, status: z.string(), spaceId: boundedId }) + .parse(await readMetadata(response)) + if (page.id !== parent.id || page.status !== 'current') return null + if (!isAllSourceItems(sourceConfig.spaceKey)) { + const space = z + .object({ key: z.string() }) + .parse( + await readMetadata( + await requestMetadata(input, `/api/v2/spaces/${encodeURIComponent(page.spaceId)}`) + ) + ) + if (!parseMultiValue(sourceConfig.spaceKey).includes(space.key)) return null + } + const labels = parseMultiValue(sourceConfig.labelFilter) + if (labels.length > 0) { + const seen = new Set() + let cursor: string | undefined + let matched = false + for (let count = 0; count < 100; count++) { + const query = new URLSearchParams({ limit: '250' }) + if (cursor) query.set('cursor', cursor) + const result = z + .object({ + results: z.array(z.object({ name: z.string() })).max(250), + _links: z.object({ next: providerCursor.optional() }).optional(), + }) + .parse(await readMetadata(await requestMetadata(input, `${path}/labels?${query}`))) + if (result.results.some((label) => labels.includes(label.name))) { + matched = true + break + } + const next = result._links?.next + if (!next) break + cursor = extractCursor(next) + if (!cursor || seen.has(cursor) || count === 99) { + throw new Error('Confluence parent labels could not be completely verified') + } + seen.add(cursor) + } + if (!matched) return null + } + return { spaceId: page.spaceId, contentType: parent.type } +} + +/** Attachment ACLs authorize the freshly verified parent, never the attachment ID as a page. */ +export async function locateConfluenceAttachment( + input: AttachmentRequest, + sourceConfig: Record, + doc: ExternalDocument +): Promise<{ id: string; spaceId: string; contentType: 'page' | 'blogpost' } | null> { + const attachment = await readAttachment(input, doc.externalId) + if (!attachment) return null + const parent = attachmentParent(attachment) + if (doc.metadata?.parentId !== parent.id || doc.metadata?.parentContentType !== parent.type) + return null + const location = await parentLocation(input, parent, sourceConfig) + return location ? { id: parent.id, ...location } : null +} + +function assertDownloadUrl(value: string): void { + const url = new URL(value) + if (url.protocol !== 'https:' || url.username || url.password) { + throw new Error('Confluence returned an unsafe attachment download URL') + } +} + +/** Downloads a version-pinned original for the shared PDF/OCR and Word parsing pipeline. */ +export async function getConfluenceAttachment( + input: AttachmentRequest, + sourceConfig: Record, + externalId: string +): Promise { + const attachment = await readAttachment(input, externalId) + if (!attachment) return null + const parent = attachmentParent(attachment) + if (!(await parentLocation(input, parent, sourceConfig))) return null + const stub = attachmentToStub(attachment, parent, input.domain) + const mimeType = attachmentMimeType(attachment) + if (!mimeType) { + return { + ...markSkipped(stub, 'Attachment is no longer a PDF or Word document'), + skippedExistingDisposition: 'replace', + } + } + if (attachment.fileSize && attachment.fileSize > CONNECTOR_MAX_FILE_BYTES) { + return markSkipped(stub, sizeLimitSkipReason(CONNECTOR_MAX_FILE_BYTES)) + } + const downloadUrl = `${apiBase(input.cloudId)}/rest/api/content/${encodeURIComponent(parent.id)}/child/attachment/${encodeURIComponent(attachment.id)}/download?version=${attachment.version.number}` + const redirect = await fetchWithRetry(downloadUrl, { + headers: { Authorization: `Bearer ${input.accessToken}` }, + redirect: 'manual', + signal: signalFor(input), + }) + if (redirect.status === 401) throw await unauthorizedAttachmentError(redirect) + const location = redirect.headers.get('location') + await redirect.body?.cancel() + if (redirect.status === 404) return null + if (redirect.status !== 302 || !location) { + throw new Error( + `Confluence attachment download did not return a download redirect: ${redirect.status}` + ) + } + const target = new URL(location, downloadUrl).toString() + assertDownloadUrl(target) + try { + const response = await secureFetchWithRetry(target, { + profile: 'contentFetch', + method: 'GET', + maxResponseBytes: CONNECTOR_MAX_FILE_BYTES, + signal: signalFor(input), + assertRedirectTarget: assertDownloadUrl, + stripAuthOnRedirect: true, + logUrlValidationDetails: false, + }) + if (!response.ok) { + await response.body?.cancel() + throw new Error(`Failed to download Confluence attachment: ${response.status}`) + } + const bytes = await readBodyWithLimit(response, CONNECTOR_MAX_FILE_BYTES) + if (!bytes) return markSkipped(stub, sizeLimitSkipReason(CONNECTOR_MAX_FILE_BYTES)) + if (bytes.length === 0) + return { + ...markSkipped(stub, 'Document contains no extractable text'), + skippedExistingDisposition: 'replace', + } + return { + ...stub, + contentDeferred: false, + sourceFile: { bytes, fileName: attachment.title, mimeType }, + } + } catch (error) { + if (isPayloadSizeLimitError(error)) + return markSkipped(stub, sizeLimitSkipReason(CONNECTOR_MAX_FILE_BYTES)) + throw error + } +} diff --git a/apps/sim/connectors/confluence/bulk-spaces.test.ts b/apps/sim/connectors/confluence/bulk-spaces.test.ts index f2e670030a6..0b1227efc20 100644 --- a/apps/sim/connectors/confluence/bulk-spaces.test.ts +++ b/apps/sim/connectors/confluence/bulk-spaces.test.ts @@ -14,7 +14,11 @@ function requestUrl(input: string | URL | Request): URL { beforeEach(() => { fetchMock.mockReset() - vi.stubGlobal('fetch', fetchMock) + vi.stubGlobal('fetch', (input: Parameters[0], init?: RequestInit) => { + return requestUrl(input).pathname.endsWith('/attachments') + ? Promise.resolve(Response.json({ results: [] })) + : fetchMock(input, init) + }) }) afterEach(() => vi.unstubAllGlobals()) diff --git a/apps/sim/connectors/confluence/confluence.test.ts b/apps/sim/connectors/confluence/confluence.test.ts index 894292d982a..b8428157b8d 100644 --- a/apps/sim/connectors/confluence/confluence.test.ts +++ b/apps/sim/connectors/confluence/confluence.test.ts @@ -7,7 +7,6 @@ import { AtlassianSiteNotMatchedError, } from '@/lib/atlassian/discovery' import { - buildLastModifiedClause, confluenceConnector, confluenceStorageToPlainText, confluenceViewToPlainText, @@ -19,6 +18,97 @@ import { } from '@/connectors/confluence/confluence' import { extractCursor } from '@/connectors/confluence/cursor' +/** Existing page fixtures have no files; attachment traversal has its own regression suite. */ +function stubFetchWithoutAttachments(mockFetch: typeof fetch): void { + vi.stubGlobal( + 'fetch', + vi.fn((input: Parameters[0], init?: RequestInit) => { + const url = new URL(input instanceof Request ? input.url : String(input)) + return url.pathname.endsWith('/attachments') + ? Promise.resolve(Response.json({ results: [] })) + : mockFetch(input, init) + }) + ) +} + +describe('Confluence dynamic All scope', () => { + afterEach(() => vi.unstubAllGlobals()) + + it('lists newly accessible spaces on each sync, including old content, and follows pagination', async () => { + const fetchMock = vi.fn() + const page = (id: string, key: string) => ({ + id, + type: 'page', + status: 'current', + title: id, + space: { key }, + version: { number: 1 }, + }) + fetchMock + .mockResolvedValueOnce( + new Response( + JSON.stringify({ results: [page('1', 'ENG')], _links: { next: '?cursor=next' } }) + ) + ) + .mockResolvedValueOnce(new Response(JSON.stringify({ results: [page('2', 'HR')] }))) + .mockResolvedValueOnce(new Response(JSON.stringify({ results: [page('3', 'NEW')] }))) + stubFetchWithoutAttachments(fetchMock) + const config = { + domain: 'example.atlassian.net', + spaceKey: ['*'], + contentType: 'all', + labelFilter: 'published', + } + const context = { cloudId: 'cloud-1' } + const first = await confluenceConnector.listDocuments( + 'token', + config, + undefined, + context, + new Date() + ) + const second = await confluenceConnector.listDocuments( + 'token', + config, + first.nextCursor, + context, + new Date() + ) + const nextSync = await confluenceConnector.listDocuments( + 'token', + config, + undefined, + { cloudId: 'cloud-1' }, + new Date() + ) + expect(first.hasMore).toBe(true) + expect(second.documents[0].externalId).toBe('2') + expect(second.hasMore).toBe(false) + expect(nextSync.documents[0].externalId).toBe('3') + const urls = fetchMock.mock.calls.map(([input]) => new URL(String(input))) + expect(urls.map((url) => url.searchParams.get('cql'))).toEqual( + Array(3).fill('type in ("page","blogpost") AND label="published"') + ) + expect(urls[1].searchParams.get('cursor')).toBe('next') + }) + + it.each([ + {}, + { results: [], _links: { next: '?broken=cursor' } }, + { results: [], _links: { next: '?cursor=repeat' } }, + ])('rejects an incomplete search response %#', async (body) => { + stubFetchWithoutAttachments(vi.fn().mockResolvedValue(new Response(JSON.stringify(body)))) + await expect( + confluenceConnector.listDocuments( + 'token', + { domain: 'example.atlassian.net', spaceKey: '*' }, + 'repeat', + { cloudId: 'cloud-1' } + ) + ).rejects.toThrow(/invalid|repeated/) + }) +}) + describe('Confluence service-account scopes', () => { it('requests metadata and role reads needed for complete mirrored ACLs', () => { expect(confluenceConnector.auth.mode).toBe('oauth') @@ -60,33 +150,13 @@ describe('escapeCql', () => { }) }) -describe('buildLastModifiedClause', () => { - const now = new Date('2026-09-01T12:00:00Z') - - it.concurrent('rounds the watermark up to whole minutes relative to the server clock', () => { - expect(buildLastModifiedClause(new Date('2026-09-01T11:30:30Z'), now)).toBe( - 'lastModified >= now("-30m")' - ) - }) - - it.concurrent('never asks for less than a minute', () => { - expect(buildLastModifiedClause(now, now)).toBe('lastModified >= now("-1m")') - expect(buildLastModifiedClause(new Date(now.getTime() + 60_000), now)).toBe( - 'lastModified >= now("-1m")' - ) - }) -}) - describe('Confluence rejected credentials', () => { afterEach(() => vi.unstubAllGlobals()) it.each(['discovery', 'space', 'pages', 'cql', 'content'] as const)( 'preserves authenticated401 at the %s boundary', async (boundary) => { - vi.stubGlobal( - 'fetch', - vi.fn(async () => new Response('', { status: 401 })) - ) + stubFetchWithoutAttachments(vi.fn(async () => new Response('', { status: 401 }))) const config = { domain: 'revocation-fixture.atlassian.net', spaceKey: 'ENG', @@ -548,69 +618,6 @@ describe('confluenceViewToPlainText', () => { }) }) -describe('confluence incremental CQL listing', () => { - const fetchMock = - vi.fn<(input: string | URL | Request, init?: RequestInit) => Promise>() - - function jsonResponse(body: unknown): Response { - return new Response(JSON.stringify(body), { - status: 200, - headers: { 'Content-Type': 'application/json' }, - }) - } - - function cqlOfCall(index: number): string | null { - return new URL(String(fetchMock.mock.calls[index][0])).searchParams.get('cql') - } - - beforeEach(() => { - vi.useFakeTimers() - fetchMock.mockReset() - vi.stubGlobal('fetch', fetchMock) - }) - - afterEach(() => { - vi.unstubAllGlobals() - vi.useRealTimers() - }) - - it('keeps one lastModified clause across pages that straddle a minute boundary', async () => { - const lastSyncAt = new Date('2026-09-01T11:30:00Z') - const config = { domain: 'example.atlassian.net', spaceKey: 'ENG' } - const syncContext: Record = { cloudId: 'cloud-1' } - fetchMock - .mockResolvedValueOnce( - jsonResponse({ - results: [], - _links: { next: '/wiki/rest/api/content/search?cursor=page-2&cql=ignored' }, - }) - ) - .mockResolvedValueOnce(jsonResponse({ results: [] })) - - vi.setSystemTime(new Date('2026-09-01T12:00:59Z')) - const first = await confluenceConnector.listDocuments( - 'token', - config, - undefined, - syncContext, - lastSyncAt - ) - expect(first.nextCursor).toBe('page-2') - - vi.setSystemTime(new Date('2026-09-01T12:01:01Z')) - await confluenceConnector.listDocuments( - 'token', - config, - first.nextCursor, - syncContext, - lastSyncAt - ) - - expect(cqlOfCall(0)).toContain('lastModified >= now("-31m")') - expect(cqlOfCall(1)).toBe(cqlOfCall(0)) - }) -}) - describe('Confluence service-account site binding', () => { const config = { domain: 'other.atlassian.net', spaceKey: 'ENG' } const context = { cloudId: 'cloud-1', credentialDomain: 'bound.atlassian.net' } @@ -619,7 +626,7 @@ describe('Confluence service-account site binding', () => { beforeEach(() => { fetchMock.mockReset() fetchMock.mockRejectedValue(new Error('Unexpected provider request')) - vi.stubGlobal('fetch', fetchMock) + stubFetchWithoutAttachments(fetchMock) }) afterEach(() => vi.unstubAllGlobals()) @@ -696,7 +703,7 @@ describe('Confluence listing limits', () => { beforeEach(() => { fetchMock.mockReset() - vi.stubGlobal('fetch', fetchMock) + stubFetchWithoutAttachments(fetchMock) context = { cloudId: 'cloud-1', spaceId: 'space-1' } }) @@ -807,7 +814,9 @@ describe('Confluence listing limits', () => { ) expect(result.documents).toHaveLength(4) expect(result.hasMore).toBe(true) - expect(JSON.parse(result.nextCursor!)).toEqual({ + expect( + JSON.parse(JSON.parse(result.nextCursor!.slice('attachments:'.length)).parentCursor) + ).toEqual({ page: 'next-page', blog: 'next-blog', pagesDone: false, @@ -954,8 +963,7 @@ describe('Confluence permission-scoped content', () => { const view = '

    Shared handbook

    CONFIDENTIAL SALARY DATA

    Local information

    ' beforeEach(() => { - vi.stubGlobal( - 'fetch', + stubFetchWithoutAttachments( vi.fn(async (input: string | URL | Request) => { const format = new URL(String(input)).searchParams.get('body-format') return new Response( @@ -1198,6 +1206,7 @@ describe('Confluence permission-scoped content', () => { } vi.mocked(fetch).mockImplementation(async (input) => { const url = new URL(String(input)) + if (url.pathname.endsWith('/attachments')) return Response.json({ results: [] }) if (url.pathname.endsWith('/spaces')) { return new Response(JSON.stringify({ results: [{ id: 'space-1', key: 'ENG' }] })) } @@ -1297,7 +1306,7 @@ describe('confluence mirrored permissions', () => { beforeEach(() => { fetchMock.mockReset() - vi.stubGlobal('fetch', fetchMock) + stubFetchWithoutAttachments(fetchMock) }) afterEach(() => { @@ -1316,12 +1325,13 @@ describe('confluence mirrored permissions', () => { 'token', { domain: 'example.atlassian.net', spaceKey: ['ENG', 'HR'] }, [page('eng-page', 'ENG'), page('hr-post', 'HR', 'blogpost')], - { cloudId: 'cloud-1' } + { cloudId: 'cloud-1' }, + { persistGroupMembership: vi.fn().mockResolvedValue(undefined) } ) expect(acls).toEqual({ - 'eng-page': ['s:confluence:-:acc-eng'], - 'hr-post': ['s:confluence:-:acc-hr'], + 'eng-page': ['g:confluence:cloud-1:space-readers:1'], + 'hr-post': ['g:confluence:cloud-1:space-readers:2'], }) /** A blog post has no ancestors and is never asked for them. */ const asked = fetchMock.mock.calls.map(([input]) => new URL(String(input)).pathname) @@ -1344,10 +1354,66 @@ describe('confluence mirrored permissions', () => { 'token', { domain: 'example.atlassian.net', spaceKey: 'ENG' }, [page('eng-page', 'ENG'), page('broken', 'ENG')], - { cloudId: 'cloud-1' } + { cloudId: 'cloud-1' }, + { persistGroupMembership: vi.fn().mockResolvedValue(undefined) } + ) + + expect(acls).toEqual({ 'eng-page': ['g:confluence:cloud-1:space-readers:1'] }) + }) + + it('refreshes and persists a shared space audience once for the entire document batch', async () => { + site() + const persistGroupMembership = vi.fn().mockResolvedValue(undefined) + const acls = await confluenceConnector.getDocumentAcls?.( + 'token', + { domain: 'example.atlassian.net', spaceKey: 'ENG' }, + [page('first', 'ENG'), page('second', 'ENG')], + { cloudId: 'cloud-1' }, + { persistGroupMembership } ) + expect(Object.keys(acls ?? {})).toEqual(['first', 'second']) + expect(persistGroupMembership).toHaveBeenCalledOnce() + expect(persistGroupMembership).toHaveBeenCalledWith({ + providerId: 'confluence', + tenantId: 'cloud-1', + group: expect.objectContaining({ id: 'space-readers:1' }), + memberTokens: ['s:confluence:-:acc-eng'], + }) + expect( + fetchMock.mock.calls.filter(([url]) => + String(url).endsWith('/spaces/1/permissions?limit=250') + ) + ).toHaveLength(1) + }) - expect(acls).toEqual({ 'eng-page': ['s:confluence:-:acc-eng'] }) + it('withholds only the failed space when its audience cannot be refreshed', async () => { + site() + const healthy = fetchMock.getMockImplementation()! + fetchMock.mockImplementation(async (input, init) => + String(input).includes('/spaces/2/permissions') ? jsonResponse({}, 403) : healthy(input, init) + ) + const persistGroupMembership = vi.fn().mockResolvedValue(undefined) + const acls = await confluenceConnector.getDocumentAcls?.( + 'token', + { domain: 'example.atlassian.net', spaceKey: ['ENG', 'HR'] }, + [page('eng', 'ENG'), page('hr', 'HR')], + { cloudId: 'cloud-1' }, + { persistGroupMembership } + ) + expect(acls).toEqual({ eng: ['g:confluence:cloud-1:space-readers:1'] }) + expect(persistGroupMembership).toHaveBeenCalledOnce() + }) + + it('withholds a space ACL when its verified audience could not be persisted', async () => { + site() + const acls = await confluenceConnector.getDocumentAcls?.( + 'token', + { domain: 'example.atlassian.net', spaceKey: 'ENG' }, + [page('eng', 'ENG')], + { cloudId: 'cloud-1' }, + { persistGroupMembership: vi.fn().mockRejectedValue(new Error('database unavailable')) } + ) + expect(acls).toEqual({}) }) it('loads restrictions above the first ancestor batch even when the page and parent already restrict access', async () => { @@ -1375,10 +1441,11 @@ describe('confluence mirrored permissions', () => { 'token', { domain: 'example.atlassian.net', spaceKey: 'ENG' }, [page('eng-page', 'ENG')], - { cloudId: 'cloud-1' } + { cloudId: 'cloud-1' }, + { persistGroupMembership: vi.fn().mockResolvedValue(undefined) } ) expect(acls?.['eng-page']).toEqual({ - acl: ['s:confluence:-:acc-eng'], + acl: ['g:confluence:cloud-1:space-readers:1'], requirements: expect.arrayContaining([ ['g:confluence:cloud-1:group-eng-page'], ['g:confluence:cloud-1:group-parent'], diff --git a/apps/sim/connectors/confluence/confluence.ts b/apps/sim/connectors/confluence/confluence.ts index f0da25bdb44..8a079f06fe6 100644 --- a/apps/sim/connectors/confluence/confluence.ts +++ b/apps/sim/connectors/confluence/confluence.ts @@ -18,17 +18,29 @@ import { type RetryOptions, VALIDATE_RETRY_OPTIONS, } from '@/lib/knowledge/documents/utils' +import { + getConfluenceAttachment, + isConfluenceAttachment, + listConfluenceAttachments, + locateConfluenceAttachment, +} from '@/connectors/confluence/attachments' import { extractCursor } from '@/connectors/confluence/cursor' import { confluenceConnectorMeta } from '@/connectors/confluence/meta' import { describeContent, getReadRestriction, listAncestorIds, - listSpaceReadPrincipals, + listConfluenceSpaceMembership, openConfluenceDirectory, validateConfluencePermissionAccess, } from '@/connectors/confluence/permissions' -import type { ConnectorConfig, ExternalDocument, ExternalDocumentList } from '@/connectors/types' +import { getSourceSelectionError, isAllSourceItems } from '@/connectors/selection' +import type { + ConnectorAclContext, + ConnectorConfig, + ExternalDocument, + ExternalDocumentList, +} from '@/connectors/types' import { htmlToPlainText, joinTagArray, @@ -598,6 +610,7 @@ const ACL_CONCURRENCY = 8 /** Where a listed piece of content lives, as the permission pass needs it. */ interface ContentLocation { + id: string spaceId: string contentType: string } @@ -606,9 +619,9 @@ interface ContentLocation { * Resolves who may read each listed page. * * Confluence reports a page's restrictions only when asked for that page, so - * unlike Drive this cannot ride along with the listing. Two things are cached - * for the batch: each space's read principals and each page's restriction, - * which may be consulted by many descendants. + * unlike Drive this cannot ride along with the listing. Space IDs and page + * restrictions are cached for descendants within the batch. Space audiences + * are refreshed and persisted on demand using this source's credential. * * A page falls back to *its own* space's readers, never the union of every * configured space: a connector over two spaces must not let a reader of one @@ -621,35 +634,66 @@ async function resolveConfluenceAcls( accessToken: string, sourceConfig: Record, documents: readonly ExternalDocument[], - syncContext?: Record + syncContext?: Record, + aclContext?: ConnectorAclContext ): Promise> { + const selectionError = getSourceSelectionError(sourceConfig.spaceKey) + if (selectionError) throw new Error(selectionError) const cloudId = await resolveCloudId(accessToken, sourceConfig, syncContext) const spaceIdForKey = memoizeAsync((spaceKey: string) => resolveSpaceId(cloudId, accessToken, spaceKey) ) - const spacePrincipalsFor = memoizeAsync((spaceId: string) => - listSpaceReadPrincipals(cloudId, accessToken, spaceId) - ) const readRestriction = memoizeAsync((contentId: string) => getReadRestriction(cloudId, accessToken, contentId) ) + const spaceAudience = memoizeAsync(async (spaceId: string) => { + if (!aclContext) throw new Error('Confluence space membership persistence is unavailable') + const membership = await listConfluenceSpaceMembership( + CONFLUENCE_ACL_PROVIDER_ID, + cloudId, + accessToken, + spaceId + ) + await aclContext.persistGroupMembership({ + providerId: CONFLUENCE_ACL_PROVIDER_ID, + tenantId: cloudId, + group: membership.group, + memberTokens: membership.memberTokens, + }) + return membership.group.id + }) + /** The listing usually says where a page lives; anything it did not describe is asked. */ const locate = async (doc: ExternalDocument): Promise => { + if (isConfluenceAttachment(doc.externalId)) { + return locateConfluenceAttachment( + { + accessToken, + cloudId, + domain: normalizeConfluenceDomainHost(sourceConfig.domain as string), + syncContext, + }, + sourceConfig, + doc + ) + } const spaceKey = doc.metadata?.spaceKey const contentType = doc.metadata?.contentType if (typeof spaceKey === 'string' && spaceKey) { return { + id: doc.externalId, spaceId: await spaceIdForKey(spaceKey), contentType: typeof contentType === 'string' ? contentType : 'page', } } - return describeContent(cloudId, accessToken, doc.externalId) + const location = await describeContent(cloudId, accessToken, doc.externalId) + return location ? { id: doc.externalId, ...location } : null } /** One entry per page whose permissions this run could read in full. */ - const resolved = new Map() + const resolved = new Map() let unreadable = 0 await mapWithConcurrency(documents, ACL_CONCURRENCY, async (doc) => { const externalId = doc.externalId @@ -659,20 +703,19 @@ async function resolveConfluenceAcls( unreadable += 1 return } - const own = await readRestriction(externalId) + const own = await readRestriction(location.id) /** * Every ancestor restriction still applies when the page has its own. * A blog post has no ancestors to inherit from. */ const chain: ConfluenceRestriction[] = [own] if (location.contentType !== 'blogpost') { - for (const ancestorId of await listAncestorIds(cloudId, accessToken, externalId)) { + for (const ancestorId of await listAncestorIds(cloudId, accessToken, location.id)) { const restriction = await readRestriction(ancestorId) chain.push(restriction) } } - await spacePrincipalsFor(location.spaceId) - resolved.set(externalId, { spaceId: location.spaceId, chain }) + resolved.set(externalId, { spaceGroupId: await spaceAudience(location.spaceId), chain }) } catch (error) { unreadable += 1 logger.warn("Could not verify a page's permissions", { @@ -684,9 +727,9 @@ async function resolveConfluenceAcls( }) const acls: Record = {} - for (const [externalId, { spaceId, chain }] of resolved) { + for (const [externalId, { spaceGroupId, chain }] of resolved) { const result = confluencePageAcl({ - spacePrincipals: await spacePrincipalsFor(spaceId), + spacePrincipals: [{ kind: 'group', id: spaceGroupId }], restrictionChain: chain, providerId: CONFLUENCE_ACL_PROVIDER_ID, tenantId: cloudId, @@ -706,83 +749,108 @@ async function resolveConfluenceAcls( return acls } -export const confluenceConnector: ConnectorConfig = { - isCredentialInvalidError: (error) => - error instanceof Error && 'status' in error && error.status === 401, - ...confluenceConnectorMeta, - - listDocuments: async ( - accessToken: string, - sourceConfig: Record, - cursor?: string, - syncContext?: Record, - lastSyncAt?: Date - ): Promise => { - const domain = normalizeConfluenceDomainHost(sourceConfig.domain as string) - const spaceKeys = parseMultiValue(sourceConfig.spaceKey) - const contentType = (sourceConfig.contentType as string) || 'page' - const labelFilter = (sourceConfig.labelFilter as string) || '' - const maxPages = sourceConfig.maxPages ? Number(sourceConfig.maxPages) : 0 - - if (spaceKeys.length === 0) { - throw new Error('At least one space key is required') - } - - const cloudId = await resolveCloudId(accessToken, sourceConfig, syncContext) +async function listParentDocuments( + accessToken: string, + sourceConfig: Record, + cursor?: string, + syncContext?: Record +): Promise { + const domain = normalizeConfluenceDomainHost(sourceConfig.domain as string) + const allSpaces = isAllSourceItems(sourceConfig.spaceKey) + const spaceKeys = parseMultiValue(sourceConfig.spaceKey) + const contentType = (sourceConfig.contentType as string) || 'page' + const labelFilter = (sourceConfig.labelFilter as string) || '' + const maxPages = sourceConfig.maxPages ? Number(sourceConfig.maxPages) : 0 + + if (spaceKeys.length === 0) { + throw new Error('At least one space key is required') + } - /** - * Route through CQL when a label filter is set, when multiple spaces are - * selected, or when only recently modified content is wanted — the v2 - * `/spaces/{spaceId}/pages` endpoint is single-space only and cannot filter - * by modification time, but CQL natively supports `space in (...)` and - * `lastModified`. - */ - if (labelFilter.trim() || spaceKeys.length > 1 || lastSyncAt) { - return listSpaceBatchesViaCql( - cloudId, - accessToken, - domain, - spaceKeys, - contentType, - labelFilter, - maxPages, - cursor, - syncContext, - lastSyncAt - ) - } + const cloudId = await resolveCloudId(accessToken, sourceConfig, syncContext) - const spaceKey = spaceKeys[0] - let spaceId = syncContext?.spaceId as string | undefined - if (!spaceId) { - spaceId = await resolveSpaceId(cloudId, accessToken, spaceKey) - if (syncContext) syncContext.spaceId = spaceId - } + /** + * Route through CQL when a label filter is set, when multiple spaces are + * selected — the v2 space endpoint cannot apply those filters. + */ + if (allSpaces) { + return listDocumentsViaCql( + cloudId, + accessToken, + domain, + [], + contentType, + labelFilter, + maxPages, + cursor, + syncContext + ) + } + if (labelFilter.trim() || spaceKeys.length > 1) { + return listSpaceBatchesViaCql( + cloudId, + accessToken, + domain, + spaceKeys, + contentType, + labelFilter, + maxPages, + cursor, + syncContext + ) + } - if (contentType === 'all') { - return listAllContentTypes( - cloudId, - accessToken, - domain, - spaceId, - spaceKey, - maxPages, - cursor, - syncContext - ) - } + const spaceKey = spaceKeys[0] + let spaceId = syncContext?.spaceId as string | undefined + if (!spaceId) { + spaceId = await resolveSpaceId(cloudId, accessToken, spaceKey) + if (syncContext) syncContext.spaceId = spaceId + } - return listDocumentsV2( + if (contentType === 'all') { + return listAllContentTypes( cloudId, accessToken, domain, spaceId, spaceKey, - contentType, maxPages, cursor, syncContext ) + } + + return listDocumentsV2( + cloudId, + accessToken, + domain, + spaceId, + spaceKey, + contentType, + maxPages, + cursor, + syncContext + ) +} + +export const confluenceConnector: ConnectorConfig = { + isCredentialInvalidError: (error) => + error instanceof Error && 'status' in error && error.status === 401, + ...confluenceConnectorMeta, + + listDocuments: async (accessToken, sourceConfig, cursor, syncContext) => { + const selectionError = getSourceSelectionError(sourceConfig.spaceKey) + if (selectionError) throw new Error(selectionError) + const cloudId = await resolveCloudId(accessToken, sourceConfig, syncContext) + return listConfluenceAttachments({ + accessToken, + cloudId, + domain: normalizeConfluenceDomainHost(sourceConfig.domain as string), + cursor, + syncContext, + /** Attachment versions change independently of their parent pages. */ + listParents: (parentCursor, parentContext) => + listParentDocuments(accessToken, sourceConfig, parentCursor, parentContext), + }) }, getDocumentAcls: resolveConfluenceAcls, @@ -800,9 +868,19 @@ export const confluenceConnector: ConnectorConfig = { externalId: string, syncContext?: Record ): Promise => { + const selectionError = getSourceSelectionError(sourceConfig.spaceKey) + if (selectionError) throw new Error(selectionError) const domain = normalizeConfluenceDomainHost(sourceConfig.domain as string) const cloudId = await resolveCloudId(accessToken, sourceConfig, syncContext) + if (isConfluenceAttachment(externalId)) { + return getConfluenceAttachment( + { accessToken, cloudId, domain, syncContext }, + sourceConfig, + externalId + ) + } + const scopedContent = usesPermissionScopedContent(syncContext) const bodyFormat = scopedContent ? 'storage' : 'view' let page: Record | null = null @@ -874,7 +952,10 @@ export const confluenceConnector: ConnectorConfig = { sourceConfig: Record, syncContext?: Record ): Promise<{ valid: boolean; error?: string }> => { + const selectionError = getSourceSelectionError(sourceConfig.spaceKey) + if (selectionError) return { valid: false, error: selectionError } const domain = sourceConfig.domain as string + const allSpaces = isAllSourceItems(sourceConfig.spaceKey) const spaceKeys = parseMultiValue(sourceConfig.spaceKey) if (!domain || spaceKeys.length === 0) { @@ -897,14 +978,14 @@ export const confluenceConnector: ConnectorConfig = { : VALIDATE_RETRY_OPTIONS const cloudId = await resolveCloudId(accessToken, sourceConfig, syncContext, retryOptions) let permissionSpaceId: string | undefined - for (const batch of spaceKeyBatches(spaceKeys)) { + for (const batch of allSpaces ? [[]] : spaceKeyBatches(spaceKeys)) { const remainingKeys = new Set(batch) const seenCursors = new Set() let cursor: string | undefined do { const params = new URLSearchParams() for (const key of batch) params.append('keys', key) - params.set('limit', String(batch.length)) + params.set('limit', String(allSpaces ? 1 : batch.length)) if (cursor) params.set('cursor', cursor) const response = await fetchWithRetry( `https://api.atlassian.com/ex/confluence/${cloudId}/wiki/api/v2/spaces?${params}`, @@ -923,7 +1004,7 @@ export const confluenceConnector: ConnectorConfig = { } for (const space of data.results) { if ( - remainingKeys.delete(space.key) && + (allSpaces || remainingKeys.delete(space.key)) && !permissionSpaceId && typeof space.id === 'string' ) { @@ -1042,7 +1123,8 @@ async function listDocumentsV2( } const data = await response.json() - const results = data.results || [] + if (!Array.isArray(data.results)) throw new Error('Confluence returned an invalid content page') + const results = data.results const allDocuments: ExternalDocument[] = (results as Record[]) .filter(isCurrentContent) @@ -1060,7 +1142,11 @@ async function listDocumentsV2( ) }) - const nextCursor = extractCursor((data._links as Record | undefined)?.next) + const next = (data._links as Record | undefined)?.next + const nextCursor = extractCursor(next) + if (next && (!nextCursor || nextCursor === cursor)) { + throw new Error('Confluence returned an invalid or repeated content continuation') + } const fetchedSoFar = (syncContext?.totalDocsFetched as number) ?? 0 const remaining = maxPages > 0 ? Math.max(0, maxPages - fetchedSoFar) : Number.POSITIVE_INFINITY @@ -1173,34 +1259,6 @@ async function listAllContentTypes( return results } -/** - * The CQL clause selecting content modified since a watermark. CQL's `now()` - * takes a relative offset and evaluates on the server, which sidesteps the - * timezone the endpoint would otherwise assume for an absolute timestamp; the - * offset rounds up to the next whole minute so nothing at the edge is missed. - */ -export function buildLastModifiedClause(lastSyncAt: Date, now: Date): string { - const minutes = Math.max(1, Math.ceil((now.getTime() - lastSyncAt.getTime()) / 60_000)) - return `lastModified >= now("-${minutes}m")` -} - -/** - * The `lastModified` clause every page of one listing shares. The clause is a - * window relative to the server clock, so recomputing it on a later page that - * crosses a minute boundary would pair the cursor `_links.next` issued with a - * query it was not issued for; the first page fixes it for the run. - */ -export function resolveLastModifiedClause( - lastSyncAt: Date, - syncContext: Record | undefined -): string { - const fixed = syncContext?.cqlLastModifiedClause - if (typeof fixed === 'string') return fixed - const clause = buildLastModifiedClause(lastSyncAt, new Date()) - if (syncContext) syncContext.cqlLastModifiedClause = clause - return clause -} - /** * Page size for CQL search. The endpoint defaults to 25 and documents no hard * maximum, so this stays conservatively below the fixed system limits it warns @@ -1218,8 +1276,7 @@ async function listSpaceBatchesViaCql( labelFilter: string, maxPages: number, cursor?: string, - syncContext: Record = {}, - lastSyncAt?: Date + syncContext: Record = {} ): Promise { const batches = spaceKeyBatches(spaceKeys) if (batches.length === 1) { @@ -1232,8 +1289,7 @@ async function listSpaceBatchesViaCql( labelFilter, maxPages, cursor, - syncContext, - lastSyncAt + syncContext ) } @@ -1267,8 +1323,7 @@ async function listSpaceBatchesViaCql( labelFilter, maxPages, providerCursor, - syncContext, - lastSyncAt + syncContext ) const hasMoreBatches = batchIndex + 1 < batches.length if (maxPages > 0 && Number(syncContext.totalDocsFetched) >= maxPages) { @@ -1289,7 +1344,7 @@ async function listSpaceBatchesViaCql( } /** - * Lists documents using CQL search via the v1 API (used when label filtering is enabled). + * Lists parents through CQL for all-space, multi-space, and label-filtered sources. */ async function listDocumentsViaCql( cloudId: string, @@ -1300,29 +1355,25 @@ async function listDocumentsViaCql( labelFilter: string, maxPages: number, cursor?: string, - syncContext?: Record, - lastSyncAt?: Date + syncContext?: Record ): Promise { const labels = labelFilter .split(',') .map((l) => l.trim()) .filter(Boolean) - // Build CQL query - let cql = buildSpaceClause(spaceKeys) + let cql = spaceKeys.length > 0 ? `${buildSpaceClause(spaceKeys)} AND ` : '' if (contentType === 'blogpost') { - cql += ' AND type="blogpost"' + cql += 'type="blogpost"' } else if (contentType === 'all') { /** - * An unconstrained CQL search matches every content type the index holds — - * attachments, comments, space descriptions and user profiles included — none - * of which `getDocument` can resolve through the page/blogpost endpoints. "All - * content" means both indexable content types, not literally everything. + * Restrict parent discovery to pages and blog posts; supported attachments + * are listed separately beneath these parents. */ - cql += ' AND type in ("page","blogpost")' + cql += 'type in ("page","blogpost")' } else { - cql += ' AND type="page"' + cql += 'type="page"' } if (labels.length === 1) { @@ -1332,8 +1383,6 @@ async function listDocumentsViaCql( cql += ` AND label in (${labelList})` } - if (lastSyncAt) cql += ` AND ${resolveLastModifiedClause(lastSyncAt, syncContext)}` - const fetchedSoFar = (syncContext?.totalDocsFetched as number) ?? 0 const remaining = maxPages > 0 ? maxPages - fetchedSoFar : Number.POSITIVE_INFINITY @@ -1381,7 +1430,8 @@ async function listDocumentsViaCql( } const data = await response.json() - const results = data.results || [] + if (!Array.isArray(data.results)) throw new Error('Confluence returned an invalid search page') + const results = data.results const allDocuments: ExternalDocument[] = (results as Record[]) .filter(isCurrentContent) @@ -1396,7 +1446,11 @@ async function listDocumentsViaCql( allDocuments.length > remaining ? allDocuments.slice(0, remaining) : allDocuments const trimmedByCap = documents.length < allDocuments.length - const nextCursor = extractCursor((data._links as Record | undefined)?.next) + const next = (data._links as Record | undefined)?.next + const nextCursor = extractCursor(next) + if (next && (!nextCursor || nextCursor === cursor)) { + throw new Error('Confluence returned an invalid or repeated search continuation') + } const totalFetched = fetchedSoFar + documents.length if (syncContext) syncContext.totalDocsFetched = totalFetched diff --git a/apps/sim/connectors/confluence/meta.ts b/apps/sim/connectors/confluence/meta.ts index b7b3f0782b3..16cebaceb8e 100644 --- a/apps/sim/connectors/confluence/meta.ts +++ b/apps/sim/connectors/confluence/meta.ts @@ -1,4 +1,5 @@ import { ConfluenceIcon } from '@/components/icons' +import { ALL_SOURCE_ITEMS } from '@/connectors/selection' import type { ConnectorMeta } from '@/connectors/types' export const confluenceConnectorMeta: ConnectorMeta = { @@ -14,6 +15,7 @@ export const confluenceConnectorMeta: ConnectorMeta = { mode: 'oauth', provider: 'confluence', adminCredentialType: 'service_account', + /** Attachment access is optional so older credentials can keep syncing parent content. */ requiredScopes: [ 'read:confluence-content.all', 'read:page:confluence', @@ -28,6 +30,7 @@ export const confluenceConnectorMeta: ConnectorMeta = { 'read:confluence-content.all', 'read:page:confluence', 'read:blogpost:confluence', + 'read:attachment:confluence', 'read:space:confluence', 'read:label:confluence', 'search:confluence', @@ -78,6 +81,7 @@ export const confluenceConnectorMeta: ConnectorMeta = { mode: 'basic', multi: true, allowSelectAll: true, + selectAllValue: ALL_SOURCE_ITEMS, preserveValueOnModeChange: true, dependsOn: ['domain'], placeholder: 'Select one or more spaces', diff --git a/apps/sim/connectors/confluence/permissions.test.ts b/apps/sim/connectors/confluence/permissions.test.ts index 925230bdc21..163b16aa564 100644 --- a/apps/sim/connectors/confluence/permissions.test.ts +++ b/apps/sim/connectors/confluence/permissions.test.ts @@ -2,12 +2,13 @@ * @vitest-environment node */ import { beforeEach, describe, expect, it, vi } from 'vitest' -import { MAX_ACL_TOKENS } from '@/lib/knowledge/access/tokens' import { getReadRestriction, listAncestorIds, + listConfluenceSpaceMembership, listGroupMemberTokens, listSpaceReadPrincipals, + openConfluenceDirectory, } from '@/connectors/confluence/permissions' const mockFetch = vi.fn() @@ -26,6 +27,63 @@ beforeEach(() => { }) describe('listSpaceReadPrincipals', () => { + it('retains more than 5,000 readers in a space audience without expanding document ACLs', async () => { + let page = 0 + mockFetch.mockImplementation(async () => { + const offset = page++ * 250 + return jsonResponse({ + results: Array.from({ length: 250 }, (_, index) => ({ + principal: { type: 'user', id: `reader-${offset + index}` }, + operation: { key: 'read', targetType: 'space' }, + })), + ...(page < 24 ? { _links: { next: `?cursor=${page}` } } : {}), + }) + }) + const membership = await listConfluenceSpaceMembership('confluence', CLOUD, 'token', '123') + expect(membership.complete).toBe(true) + expect(membership.memberTokens).toHaveLength(6000) + expect(membership.memberTokens[5999]).toBe('s:confluence:-:reader-5999') + expect(mockFetch).toHaveBeenCalledTimes(24) + }) + + it('stores native reader groups without flattening their membership', async () => { + mockFetch.mockResolvedValueOnce( + jsonResponse({ + results: [ + { + principal: { type: 'group', id: 'engineering' }, + operation: { key: 'read', targetType: 'space' }, + }, + ], + }) + ) + await expect( + listConfluenceSpaceMembership('confluence', CLOUD, 'token', '123') + ).resolves.toEqual({ + group: { id: 'space-readers:123' }, + memberTokens: ['g:confluence:cloud-1:engineering'], + complete: true, + }) + }) + + it('lists only site-native groups without enumerating unrelated visible spaces', async () => { + mockFetch.mockResolvedValueOnce(jsonResponse({ results: [{ id: 'engineering' }] })) + const directory = openConfluenceDirectory('confluence', CLOUD, 'token') + await expect(directory.listGroups()).resolves.toEqual([{ id: 'engineering' }]) + expect(mockFetch).toHaveBeenCalledOnce() + expect(mockFetch.mock.calls[0][0]).toContain('/rest/api/group?') + }) + + it.each(['space-readers:123', ' SPACE-READERS:123 '])( + 'rejects native groups in the reserved namespace: %s', + async (id) => { + mockFetch.mockResolvedValueOnce(jsonResponse({ results: [{ id }] })) + await expect( + openConfluenceDirectory('confluence', CLOUD, 'token').listGroups() + ).rejects.toThrow('invalid group ID') + } + ) + it('keeps only the permission that grants reading the space', () => { mockFetch.mockResolvedValueOnce( jsonResponse({ @@ -419,9 +477,9 @@ describe('listSpaceReadPrincipals', () => { }) ) await expect(listSpaceReadPrincipals(CLOUD, 'token', 'space')).rejects.toThrow( - 'document permission limit' + 'directory capacity' ) - expect(mockFetch).toHaveBeenCalledTimes(Math.floor(MAX_ACL_TOKENS / 250) + 1) + expect(mockFetch).toHaveBeenCalledTimes(401) }) it('rejects an oversized permission response before accepting its readers', async () => { diff --git a/apps/sim/connectors/confluence/permissions.ts b/apps/sim/connectors/confluence/permissions.ts index 5e8e8f1470e..af9c8642281 100644 --- a/apps/sim/connectors/confluence/permissions.ts +++ b/apps/sim/connectors/confluence/permissions.ts @@ -5,9 +5,11 @@ import { readResponseJsonWithLimit } from '@/lib/core/utils/stream-limits' import { type ConfluencePrincipal, type ConfluenceRestriction, + confluenceSpaceGroupId, confluenceSubjectToken, } from '@/lib/knowledge/access/confluence-permissions' -import { MAX_ACL_TOKENS } from '@/lib/knowledge/access/tokens' +import { CONFLUENCE_SPACE_GROUP_PREFIX } from '@/lib/knowledge/access/confluence-space-groups' +import { canonicalGroupId, groupToken } from '@/lib/knowledge/access/tokens' import { fetchWithRetry } from '@/lib/knowledge/documents/secure-fetch.server' import type { RetryOptions } from '@/lib/knowledge/documents/utils' import { extractCursor } from '@/connectors/confluence/cursor' @@ -26,10 +28,20 @@ const GROUP_PAGE_SIZE = 200 const MAX_PAGES = 100 const MAX_PERMISSION_PAGES = 1000 const PERMISSION_RESPONSE_MAX_BYTES = 1024 * 1024 +const MAX_SPACE_READERS = 100_000 +const MAX_SPACE_READER_BYTES = 16 * 1024 * 1024 const MAX_PERMISSION_CURSOR_LENGTH = 8192 const PERMISSION_COLLECTION_TIMEOUT_MS = 5 * 60 * 1000 const PREFLIGHT_RESPONSE_MAX_BYTES = 256 * 1024 +function validateNativeGroupId(id: string): string { + const canonical = canonicalGroupId(id) + if (!canonical || canonical.startsWith(CONFLUENCE_SPACE_GROUP_PREFIX)) { + throw new Error('Confluence returned an invalid group ID') + } + return id +} + function apiBase(cloudId: string): string { return `https://api.atlassian.com/ex/confluence/${cloudId}/wiki` } @@ -313,11 +325,18 @@ export async function listSpaceReadPrincipals( if (!id) { unmapped += 1 } else if (type === 'user' || type === 'group') { + if (type === 'group') validateNativeGroupId(id) const key = `${type}:${id}` if (!principals.has(key)) { principalBytes += Buffer.byteLength(key, 'utf8') - if (principals.size >= MAX_ACL_TOKENS || principalBytes > PERMISSION_RESPONSE_MAX_BYTES) { - throw new Error('Confluence space readers exceeded the document permission limit') + if (principals.size >= MAX_SPACE_READERS || principalBytes > MAX_SPACE_READER_BYTES) { + logger.warn('Confluence space audience exceeded the directory capacity', { + cloudId, + spaceId, + principals: principals.size, + principalBytes, + }) + throw new Error('Confluence space readers exceeded the directory capacity') } principals.set(key, { kind: type, id }) } @@ -477,6 +496,7 @@ export async function getReadRestriction( } for (const group of groups) { if (!group.id) throw new Error('Confluence read restriction is missing a group id') + validateNativeGroupId(group.id) principals.set(`group:${group.id}`, { kind: 'group', id: group.id }) } const nextStarts = [ @@ -565,7 +585,7 @@ async function listSiteGroups( ) const groups: ConnectorDirectoryGroup[] = [] for (const group of raw) { - if (group.id) groups.push({ id: group.id }) + groups.push({ id: validateNativeGroupId(group.id ?? '') }) } return groups } @@ -602,3 +622,21 @@ export function openConfluenceDirectory( listGroupMembers: (group) => listGroupMemberTokens(cloudId, accessToken, group), } } + +/** Only spaces encountered by this credential's content crawl need an audience refresh. */ +export async function listConfluenceSpaceMembership( + providerId: string, + cloudId: string, + accessToken: string, + spaceId: string +): Promise { + const group = { id: confluenceSpaceGroupId(spaceId) } + const principals = await listSpaceReadPrincipals(cloudId, accessToken, spaceId) + const memberTokens = principals.map((principal) => { + if (principal.kind === 'user') return confluenceSubjectToken(principal.id) + const token = groupToken({ providerId, tenantId: cloudId, groupId: principal.id }) + if (!token) throw new Error('Confluence returned an invalid reader group') + return token + }) + return { group, memberTokens, complete: true } +} diff --git a/apps/sim/connectors/jira/jira.test.ts b/apps/sim/connectors/jira/jira.test.ts index a2c08e7a0ff..2a3ffe7a2cc 100644 --- a/apps/sim/connectors/jira/jira.test.ts +++ b/apps/sim/connectors/jira/jira.test.ts @@ -61,6 +61,31 @@ afterEach(() => { }) describe('Jira Search member documents', () => { + it('resolves All through the current credential across pages and future syncs', async () => { + fetchMock + .mockResolvedValueOnce(json({ issues: [issue()], nextPageToken: 'page-two', isLast: false })) + .mockResolvedValueOnce( + json({ issues: [issue('20001', { project: { id: '20000', key: 'HR' } })], isLast: true }) + ) + .mockResolvedValueOnce( + json({ issues: [issue('30001', { project: { id: '30000', key: 'NEW' } })], isLast: true }) + ) + const config = { ...SOURCE, projectKey: ['*'], jql: 'status = Open' } + const context = { ...MEMBERS } + const first = await jiraConnector.listDocuments('token', config, undefined, context) + const second = await jiraConnector.listDocuments('token', config, first.nextCursor, context) + const nextSync = await jiraConnector.listDocuments('token', config, undefined, { ...MEMBERS }) + expect(first.hasMore).toBe(true) + expect(second.documents[0].sourceUrl).toContain('ENG-20001') + expect(nextSync.documents[0].sourceUrl).toContain('ENG-30001') + expect(second.hasMore).toBe(false) + const urls = fetchMock.mock.calls.map(([input]) => new URL(String(input))) + expect(urls.map((url) => url.searchParams.get('jql'))).toEqual( + Array(3).fill('project IS NOT EMPTY AND (status = Open) ORDER BY updated DESC') + ) + expect(urls[1].searchParams.get('nextPageToken')).toBe('page-two') + }) + it('offers managed member Search with canonical project setup and no central ACL mode', () => { expect(jiraConnectorMeta.search).toBe(true) expect(jiraConnectorMeta.permissionScopedListing?.capFieldIds).toEqual(['maxIssues']) diff --git a/apps/sim/connectors/jira/jira.ts b/apps/sim/connectors/jira/jira.ts index 52c0a766a89..781f1f566b3 100644 --- a/apps/sim/connectors/jira/jira.ts +++ b/apps/sim/connectors/jira/jira.ts @@ -10,6 +10,7 @@ import { import { fetchWithRetry } from '@/lib/knowledge/documents/secure-fetch.server' import { type RetryOptions, VALIDATE_RETRY_OPTIONS } from '@/lib/knowledge/documents/utils' import { jiraConnectorMeta } from '@/connectors/jira/meta' +import { getSourceSelectionError, isAllSourceItems } from '@/connectors/selection' import type { ConnectorConfig, ExternalDocument, ExternalDocumentList } from '@/connectors/types' import { computeContentHash, @@ -144,6 +145,7 @@ function getMaxIssues(sourceConfig: Record): number { * Each key is escaped for inclusion in a JQL double-quoted string. */ function buildProjectClause(projectKeys: string[]): string { + if (isAllSourceItems(projectKeys)) return 'project IS NOT EMPTY' const escapeKey = (key: string) => key.replace(/\\/g, '\\\\').replace(/"/g, '\\"') if (projectKeys.length === 1) { return `project = "${escapeKey(projectKeys[0])}"` @@ -270,8 +272,11 @@ async function issueToMemberDocument( /** JQL can refine the configured projects but must not expand their scope. */ function isInConfiguredProject(issue: JiraIssue, projectKeys: string[]): boolean { const project = issue.fields.project - return projectKeys.some( - (value) => value === project.id || value.toUpperCase() === project.key.toUpperCase() + return ( + isAllSourceItems(projectKeys) || + projectKeys.some( + (value) => value === project.id || value.toUpperCase() === project.key.toUpperCase() + ) ) } @@ -298,6 +303,8 @@ export const jiraConnector: ConnectorConfig = { cursor?: string, syncContext?: Record ): Promise => { + const selectionError = getSourceSelectionError(sourceConfig.projectKey) + if (selectionError) throw new Error(selectionError) const domain = sourceConfig.domain as string const siteUrl = normalizeAtlassianSiteUrl(domain) const projectKeys = parseMultiValue(sourceConfig.projectKey) @@ -522,6 +529,8 @@ export const jiraConnector: ConnectorConfig = { externalId: string, syncContext?: Record ): Promise => { + const selectionError = getSourceSelectionError(sourceConfig.projectKey) + if (selectionError) throw new Error(selectionError) const domain = sourceConfig.domain as string const siteUrl = normalizeAtlassianSiteUrl(domain) const cloudId = await resolveCloudId(accessToken, domain, syncContext) @@ -568,6 +577,8 @@ export const jiraConnector: ConnectorConfig = { sourceConfig: Record, syncContext?: Record ): Promise<{ valid: boolean; error?: string }> => { + const selectionError = getSourceSelectionError(sourceConfig.projectKey) + if (selectionError) return { valid: false, error: selectionError } const domain = sourceConfig.domain as string const projectKeys = parseMultiValue(sourceConfig.projectKey) diff --git a/apps/sim/connectors/jira/meta.ts b/apps/sim/connectors/jira/meta.ts index d99f48cf0d6..247a4b8bd0e 100644 --- a/apps/sim/connectors/jira/meta.ts +++ b/apps/sim/connectors/jira/meta.ts @@ -1,4 +1,5 @@ import { JiraIcon } from '@/components/icons' +import { ALL_SOURCE_ITEMS } from '@/connectors/selection' import type { ConnectorMeta } from '@/connectors/types' export const jiraConnectorMeta: ConnectorMeta = { @@ -30,6 +31,7 @@ export const jiraConnectorMeta: ConnectorMeta = { mode: 'basic', multi: true, allowSelectAll: true, + selectAllValue: ALL_SOURCE_ITEMS, preserveValueOnModeChange: true, dependsOn: ['domain'], placeholder: 'Select one or more projects', diff --git a/apps/sim/connectors/selection.ts b/apps/sim/connectors/selection.ts new file mode 100644 index 00000000000..ac8da6b8bb9 --- /dev/null +++ b/apps/sim/connectors/selection.ts @@ -0,0 +1,34 @@ +import type { ConnectorMeta } from '@/connectors/types' +import { parseMultiValue } from '@/connectors/utils' + +/** Persisted scope marker; providers resolve the accessible set during each listing. */ +export const ALL_SOURCE_ITEMS = '*' + +export function isAllSourceItems(value: unknown): boolean { + const items = parseMultiValue(value) + return items.length === 1 && items[0] === ALL_SOURCE_ITEMS +} + +export function getSourceSelectionError( + value: unknown, + allValue = ALL_SOURCE_ITEMS +): string | undefined { + const items = parseMultiValue(value) + if (items.length > 1 && items.includes(allValue)) { + return `Use "${allValue}" by itself for All, or remove it to select individual items.` + } +} + +export function findSourceSelectionError( + connector: Pick, + sourceConfig: Record +): string | undefined { + for (const field of connector.configFields) { + if (!field.selectAllValue) continue + const error = getSourceSelectionError( + sourceConfig[field.canonicalParamId ?? field.id], + field.selectAllValue + ) + if (error) return error + } +} diff --git a/apps/sim/connectors/types.ts b/apps/sim/connectors/types.ts index 5c5bc3f49b0..7014bdd8586 100644 --- a/apps/sim/connectors/types.ts +++ b/apps/sim/connectors/types.ts @@ -69,7 +69,7 @@ export interface ConnectorDirectoryGroup { export interface ConnectorDirectoryMembership { group: ConnectorDirectoryGroup - /** Canonical u:email or provider-attested s: identity tokens, with nested groups flattened. */ + /** Canonical identities; Confluence space audiences may also name same-site native groups. */ memberTokens: string[] /** * False when the walk could not be completed. A partial membership must never @@ -79,6 +79,19 @@ export interface ConnectorDirectoryMembership { complete: boolean } +/** A complete source audience observed while resolving a bounded batch of document ACLs. */ +export interface ConnectorAclGroupMembership { + providerId: string + tenantId: string + group: ConnectorDirectoryGroup + memberTokens: string[] +} + +/** Persistence capabilities bound by the engine to the canonical resource owner and sync lease. */ +export interface ConnectorAclContext { + persistGroupMembership: (membership: ConnectorAclGroupMembership) => Promise +} + /** * One directory, opened for the length of a sync. Implementations throw rather * than returning a partial listing — a truncated directory read as complete @@ -330,8 +343,10 @@ export interface ConnectorConfigField { * Connector handlers receive `string | string[]` and should normalize via `parseMultiValue`. */ multi?: boolean - /** Offers explicit bulk selection of the complete, bounded provider list. */ + /** Offers selection of all items, using selectAllValue when configured. */ allowSelectAll?: boolean + /** Stores a provider-supported scope marker instead of the currently loaded option IDs. */ + selectAllValue?: string } /** @@ -583,7 +598,8 @@ export interface ConnectorConfig extends ConnectorMeta { accessToken: string, sourceConfig: Record, documents: readonly ExternalDocument[], - syncContext?: Record + syncContext?: Record, + aclContext?: ConnectorAclContext ) => Promise> /** Map source metadata to semantic tag keys (translated to slots by the sync engine) */ diff --git a/apps/sim/lib/knowledge/__integration__/confluence-identity.integration.ts b/apps/sim/lib/knowledge/__integration__/confluence-identity.integration.ts index ea7b12dc25f..3cb48da93be 100644 --- a/apps/sim/lib/knowledge/__integration__/confluence-identity.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/confluence-identity.integration.ts @@ -23,7 +23,10 @@ import { } from '@/lib/knowledge/__integration__/seed-source-access-fixture' import { groupToken, subjectToken } from '@/lib/knowledge/access/tokens' import { readKnowledgeDocument } from '@/lib/knowledge/application/documents' -import { syncExternalDirectoryGroups } from '@/lib/knowledge/connectors/external-group-sync' +import { + persistExternalGroupMembership, + syncExternalDirectoryGroups, +} from '@/lib/knowledge/connectors/external-group-sync' describe('Confluence identities with hidden directory email', () => { const ids = createKnowledgeAclFixtureIds() @@ -45,6 +48,7 @@ describe('Confluence identities with hidden directory email', () => { const documents = [ { id: generateId(), acl: [group] }, { id: generateId(), acl: [sourceSubject] }, + { id: generateId(), acl: ['g:confluence:fixture-cloud:space-readers:123'] }, ] const principal: Principal = { kind: 'session', @@ -142,6 +146,22 @@ describe('Confluence identities with hidden directory email', () => { }), }, }) + await db.transaction((tx) => + persistExternalGroupMembership( + { + workspaceId: ids.workspaceId, + providerId: 'confluence', + tenantId: 'fixture-cloud', + group: { id: 'space-readers:123' }, + memberTokens: [ + group, + ...Array.from({ length: 6000 }, (_, index) => `s:confluence:-:other-${index}`), + ], + observedAt: new Date(), + }, + tx + ) + ) await db.insert(document).values( documents.map((fixture) => ({ id: fixture.id, diff --git a/apps/sim/lib/knowledge/__integration__/directory-sync.integration.ts b/apps/sim/lib/knowledge/__integration__/directory-sync.integration.ts index 7ced44aa61e..3ea03a7024d 100644 --- a/apps/sim/lib/knowledge/__integration__/directory-sync.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/directory-sync.integration.ts @@ -22,6 +22,7 @@ const fixture = vi.hoisted(() => ({ listGroups: vi.fn(), members: vi.fn(), listDocuments: vi.fn(), + acls: vi.fn(), enqueue: vi.fn(async (_type: string, _payload: unknown) => 'job'), })) vi.mock('@/lib/auth/internal', () => ({ verifyCronAuth: () => null })) @@ -35,6 +36,7 @@ vi.mock('@/connectors/registry.server', () => ({ name: 'Directory fixture', auth: { mode: 'apiKey', optional: true }, listDocuments: fixture.listDocuments, + getDocumentAcls: fixture.acls, getDocument: async () => { throw new Error('Unexpected hydration') }, @@ -57,13 +59,23 @@ import { import { resolveUserKnowledgeAccessScope } from '@/lib/knowledge/access/scope' import { groupToken } from '@/lib/knowledge/access/tokens' import { + persistExternalGroupMembership, refreshConnectorDirectory, syncExternalDirectoryGroups, } from '@/lib/knowledge/connectors/external-group-sync' +import { + beginListingCheckpoint, + listingFingerprint, +} from '@/lib/knowledge/connectors/listing-checkpoint' import { executeSync } from '@/lib/knowledge/connectors/sync-engine' import { GET as scheduleDirectories } from '@/app/api/knowledge/connectors/directory-sync/route' import { executeDirectorySyncJob } from '@/background/knowledge-connector-directory-sync' -import type { ConnectorDirectory, ConnectorDirectoryGroup } from '@/connectors/types' +import type { + ConnectorAclContext, + ConnectorDirectory, + ConnectorDirectoryGroup, + ExternalDocument, +} from '@/connectors/types' describe('directory failure visibility in PostgreSQL', () => { const ids = createKnowledgeAclFixtureIds() @@ -92,6 +104,7 @@ describe('directory failure visibility in PostgreSQL', () => { complete: true, })) fixture.listDocuments.mockReset().mockResolvedValue({ documents: [], hasMore: false }) + fixture.acls.mockReset().mockResolvedValue({}) await db .update(knowledgeConnector) .set({ @@ -161,6 +174,174 @@ describe('directory failure visibility in PostgreSQL', () => { return { promise, resolve } } + it('keeps disjoint credential audiences independent of native-directory freshness and pruning', async () => { + const directory = { ...directoryFixture(), providerId: 'confluence' } + const persistAudience = (spaceId: string, subject: string) => + db.transaction((tx) => + persistExternalGroupMembership( + { + workspaceId: ids.workspaceId, + providerId: directory.providerId, + tenantId: directory.tenantId, + group: { id: `space-readers:${spaceId}` }, + memberTokens: [subject], + observedAt: new Date(), + }, + tx + ) + ) + await syncExternalDirectoryGroups({ workspaceId: ids.workspaceId, directory }) + await persistAudience('1', 's:confluence:-:alice') + expect( + await syncExternalDirectoryGroups({ workspaceId: ids.workspaceId, directory }) + ).toMatchObject({ skipped: true }) + await persistAudience('2', 's:confluence:-:bob') + directory.listGroups = async () => [] + expect( + await syncExternalDirectoryGroups({ workspaceId: ids.workspaceId, directory, force: true }) + ).toMatchObject({ pruned: 1 }) + const groups = await db.select().from(knowledgeExternalGroup).where(groupsWhere(directory)) + expect(groups.map((group) => group.externalGroupId).sort()).toEqual([ + 'space-readers:1', + 'space-readers:2', + ]) + const memberships = await db + .select() + .from(knowledgeExternalGroupMember) + .where( + inArray( + knowledgeExternalGroupMember.groupId, + groups.map((group) => group.id) + ) + ) + expect(memberships.map((member) => member.subjectToken).sort()).toEqual([ + 's:confluence:-:alice', + 's:confluence:-:bob', + ]) + }) + + it('does not regrant an older audience when its delayed response arrives after a revocation', async () => { + const tenantId = generateId() + const observation = { + workspaceId: ids.workspaceId, + providerId: 'confluence', + tenantId, + group: { id: 'space-readers:1' }, + } + const newer = new Date() + await db.transaction((tx) => + persistExternalGroupMembership({ ...observation, memberTokens: [], observedAt: newer }, tx) + ) + await db.transaction((tx) => + persistExternalGroupMembership( + { + ...observation, + memberTokens: ['s:confluence:-:alice'], + observedAt: new Date(newer.getTime() - 60_000), + }, + tx + ) + ) + const [group] = await db + .select() + .from(knowledgeExternalGroup) + .where(eq(knowledgeExternalGroup.tenantId, tenantId)) + expect(group.lastSyncedAt).toEqual(newer) + expect( + await db + .select() + .from(knowledgeExternalGroupMember) + .where(eq(knowledgeExternalGroupMember.groupId, group.id)) + ).toEqual([]) + }) + + it('refreshes audience evidence and revocations when an old content generation resumes', async () => { + const pages = ['audience-first', 'audience-second'] + await db.insert(document).values( + pages.map((externalId) => ({ + id: generateId(), + knowledgeBaseId: ids.knowledgeBaseId, + connectorId: ids.connectorId, + externalId, + filename: externalId, + fileUrl: 'data:text/plain,fixture', + fileSize: 7, + mimeType: 'text/plain', + contentHash: `hash-${externalId}`, + processingStatus: 'completed', + acl: [], + })) + ) + const connector = await source() + const checkpoint = beginListingCheckpoint({ + fingerprint: listingFingerprint({ + connectorType: connector.connectorType, + credentialId: connector.credentialId, + encryptedApiKey: connector.encryptedApiKey, + sourceConfig: connector.sourceConfig, + accessMode: connector.accessMode, + }), + generationId: generateId(), + startedAt: old, + }) + await db + .update(knowledgeConnector) + .set({ listingCheckpoint: checkpoint }) + .where(eq(knowledgeConnector.id, ids.connectorId)) + fixture.listDocuments.mockImplementation(async (_token, _config, cursor) => { + const externalId = cursor ? pages[1] : pages[0] + return { + documents: [ + { + externalId, + title: externalId, + content: 'fixture', + contentHash: `hash-${externalId}`, + mimeType: 'text/plain', + }, + ], + hasMore: !cursor, + nextCursor: cursor ? undefined : 'second-page', + } + }) + const tenantId = generateId() + fixture.acls.mockImplementation( + async ( + _token, + _config, + documents: ExternalDocument[], + _syncContext, + context: ConnectorAclContext + ) => { + await context.persistGroupMembership({ + providerId: 'confluence', + tenantId, + group: { id: 'space-readers:1' }, + memberTokens: documents[0].externalId === pages[0] ? ['s:confluence:-:alice'] : [], + }) + return Object.fromEntries( + documents.map((doc) => [doc.externalId, [`g:confluence:${tenantId}:space-readers:1`]]) + ) + } + ) + const before = Date.now() + expect(await executeSync(ids.connectorId, { billingAttribution: billing })).toMatchObject({ + docsUnchanged: 2, + }) + expect(fixture.acls).toHaveBeenCalledTimes(2) + const [group] = await db + .select() + .from(knowledgeExternalGroup) + .where(eq(knowledgeExternalGroup.tenantId, tenantId)) + expect(group.lastSyncedAt!.getTime()).toBeGreaterThanOrEqual(before - 1000) + expect( + await db + .select() + .from(knowledgeExternalGroupMember) + .where(eq(knowledgeExternalGroupMember.groupId, group.id)) + ).toEqual([]) + }) + it('advances past the first 200 directories, including microsecond timestamps and competing ticks', async () => { const connectors = Array.from({ length: 201 }, () => ({ id: generateId(), @@ -425,7 +606,9 @@ describe('directory failure visibility in PostgreSQL', () => { })) await expect( executeDirectorySyncJob({ connectorId: ids.connectorId, requestId: generateId() }) - ).rejects.toThrow('Directory refresh failed: 1 group memberships could not be refreshed') + ).rejects.toThrow( + 'Directory permission sync failed. Group membership could not be fully verified.' + ) const [group] = await db .select() .from(knowledgeExternalGroup) @@ -458,7 +641,9 @@ describe('directory failure visibility in PostgreSQL', () => { it('reports a directory failure after an empty content crawl and recovers on a later sync', async () => { fixture.listGroups.mockRejectedValue(new Error('Directory API HTTP 403')) const result = await executeSync(ids.connectorId, { billingAttribution: billing }) - expect(result.error).toBe('Directory refresh failed: Directory API HTTP 403') + expect(result.error).toBe( + 'Directory permission sync failed. Group membership could not be fully verified.' + ) expect(fixture.listDocuments).toHaveBeenCalledOnce() const failed = await source() expect(failed.status).toBe('error') @@ -528,7 +713,7 @@ describe('directory failure visibility in PostgreSQL', () => { const result = await executeSync(ids.connectorId, { billingAttribution: billing }) expect(result).toMatchObject({ docsUnchanged: 2, - error: 'Directory refresh failed: Directory unavailable', + error: 'Directory permission sync failed. Group membership could not be fully verified.', }) expect(fixture.listDocuments).toHaveBeenCalledTimes(2) const indexed = await db @@ -547,10 +732,10 @@ describe('directory failure visibility in PostgreSQL', () => { fixture.listGroups.mockRejectedValue(new Error('Directory unavailable')) expect( (await executeSync(ids.connectorId, { billingAttribution: billing, fullSync: true })).error - ).toContain('Directory unavailable') + ).toContain('Directory permission sync failed') fixture.listGroups.mockClear() expect((await executeSync(ids.connectorId, { billingAttribution: billing })).error).toContain( - 'Directory unavailable' + 'Directory permission sync failed' ) expect(fixture.listGroups).toHaveBeenCalledOnce() fixture.listGroups.mockResolvedValue(ids.groups.map((id) => ({ id }))) diff --git a/apps/sim/lib/knowledge/access/confluence-permissions.ts b/apps/sim/lib/knowledge/access/confluence-permissions.ts index 094aa9687ff..a70412bacdf 100644 --- a/apps/sim/lib/knowledge/access/confluence-permissions.ts +++ b/apps/sim/lib/knowledge/access/confluence-permissions.ts @@ -1,3 +1,4 @@ +import { CONFLUENCE_SPACE_GROUP_PREFIX } from '@/lib/knowledge/access/confluence-space-groups' import { groupToken, sortAccessTokens, subjectToken } from '@/lib/knowledge/access/tokens' import { LINK_ACCESS_TOKEN } from '@/lib/knowledge/access/types' @@ -7,6 +8,12 @@ export interface ConfluencePrincipal { id: string } +/** Space audiences use a namespace rejected at native group ingestion. */ +export function confluenceSpaceGroupId(spaceId: string): string { + if (!/^\d+$/.test(spaceId)) throw new Error('Confluence returned an invalid space ID') + return `${CONFLUENCE_SPACE_GROUP_PREFIX}${spaceId}` +} + /** Atlassian account IDs are global, matching the managed OAuth /me identity. */ export function confluenceSubjectToken(accountId: string): string { return subjectToken({ diff --git a/apps/sim/lib/knowledge/access/confluence-space-groups.ts b/apps/sim/lib/knowledge/access/confluence-space-groups.ts new file mode 100644 index 00000000000..86013456592 --- /dev/null +++ b/apps/sim/lib/knowledge/access/confluence-space-groups.ts @@ -0,0 +1,6 @@ +/** Reserved for source audiences; native Confluence group ingestion rejects this namespace. */ +export const CONFLUENCE_SPACE_GROUP_PREFIX = 'space-readers:' + +export function isConfluenceSpaceGroupId(groupId: string): boolean { + return /^space-readers:\d+$/.test(groupId) +} diff --git a/apps/sim/lib/knowledge/access/group-membership.integration.ts b/apps/sim/lib/knowledge/access/group-membership.integration.ts new file mode 100644 index 00000000000..b3c39d79900 --- /dev/null +++ b/apps/sim/lib/knowledge/access/group-membership.integration.ts @@ -0,0 +1,79 @@ +import { drizzle } from 'drizzle-orm/postgres-js' +import type postgres from 'postgres' +import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest' +import { createEnterpriseSearchMigrationFixture } from '@/lib/knowledge/__integration__/migration-fixture' +import { parentGroupTokensQuery } from '@/lib/knowledge/access/group-membership' + +describe('Confluence space audience access in PostgreSQL', () => { + let fixture: Awaited> + let client: ReturnType + const cutoff = new Date('2026-09-17T00:00:00Z') + const scope = { kind: 'organization', organizationId: 'org' } as const + const token = (id: string) => `g:confluence:cloud:${id}` + + beforeAll(async () => { + fixture = await createEnterpriseSearchMigrationFixture( + process.env.KNOWLEDGE_ACL_TEST_DATABASE_URL! + ) + client = fixture.client + await fixture.migrate() + await client`INSERT INTO organization(id) VALUES ('org'), ('other')` + await client`INSERT INTO workspace(id) VALUES ('workspace')` + }) + afterAll(async () => fixture?.cleanup()) + beforeEach(async () => { + await client`DELETE FROM knowledge_external_group` + }) + + async function group( + id: string, + members: string[], + options: { + provider?: string + tenant?: string + organization?: string + workspace?: string + stale?: boolean + } = {} + ) { + await client`INSERT INTO knowledge_external_group(id, organization_id, workspace_id, provider_id, tenant_id, external_group_id, last_synced_at) + VALUES (${id}, ${options.workspace ? null : (options.organization ?? 'org')}, ${options.workspace ?? null}, + ${options.provider ?? 'confluence'}, ${options.tenant ?? 'cloud'}, ${id}, + ${options.stale ? '2026-09-16T00:00:00Z' : '2026-09-17T12:00:00Z'})` + for (const member of members) { + await client`INSERT INTO knowledge_external_group_member(group_id, subject_token) VALUES (${id}, ${member})` + } + } + + async function parents(seeds = [token('engineering')]) { + const rows = await drizzle(client).execute<{ token: string }>( + parentGroupTokensQuery(seeds, scope, cutoff) + ) + return rows.map((row) => row.token).sort() + } + + it('resolves one native-group hop without traversing arbitrary nested groups or cycles', async () => { + await group('space-readers:1', [token('engineering'), token('cycle')]) + await group('cycle', [token('space-readers:1')]) + await group('space-readers:2', [token('space-readers:1')]) + expect(await parents()).toEqual([token('space-readers:1')]) + expect(await parents([token('space-readers:1')])).toEqual([]) + }) + + it('rejects cross-owner, cross-provider, cross-tenant, and stale membership at every hop', async () => { + await group('space-readers:1', [token('engineering')]) + await group('space-readers:2', [token('engineering')], { organization: 'other' }) + await group('space-readers:3', [token('engineering')], { workspace: 'workspace' }) + await group('space-readers:4', [token('engineering')], { provider: 'jira' }) + await group('space-readers:5', [token('engineering')], { tenant: 'other' }) + await group('space-readers:6', [token('engineering')], { stale: true }) + expect(await parents()).toEqual([token('space-readers:1')]) + expect(await parents([])).toEqual([]) + }) + + it('handles more group seeds than PostgreSQL permits bind parameters', async () => { + await group('space-readers:1', [token('group-69999')]) + const seeds = Array.from({ length: 70_000 }, (_, index) => token(`group-${index}`)) + expect(await parents(seeds)).toEqual([token('space-readers:1')]) + }) +}) diff --git a/apps/sim/lib/knowledge/access/group-membership.test.ts b/apps/sim/lib/knowledge/access/group-membership.test.ts new file mode 100644 index 00000000000..0c9c2ee965f --- /dev/null +++ b/apps/sim/lib/knowledge/access/group-membership.test.ts @@ -0,0 +1,22 @@ +import { describe, expect, it } from 'vitest' +import { + assertExternalGroupTokenCapacity, + MAX_EXTERNAL_GROUP_TOKENS, +} from '@/lib/knowledge/access/group-membership' + +describe('external group token capacity', () => { + it('rejects the overflow sentinel instead of returning a truncated access set', () => { + const tokens = Array.from({ length: MAX_EXTERNAL_GROUP_TOKENS }, () => 'g:confluence:cloud:g') + expect(() => assertExternalGroupTokenCapacity(tokens)).not.toThrow() + expect(() => assertExternalGroupTokenCapacity([...tokens, 'g:confluence:cloud:last'])).toThrow( + 'token capacity' + ) + }) + + it('bounds retained token bytes independently of the number of groups', () => { + const token = `g:confluence:cloud:${'x'.repeat(1024)}` + expect(() => + assertExternalGroupTokenCapacity(Array.from({ length: 16_384 }, () => token)) + ).toThrow('token byte capacity') + }) +}) diff --git a/apps/sim/lib/knowledge/access/group-membership.ts b/apps/sim/lib/knowledge/access/group-membership.ts new file mode 100644 index 00000000000..bf5f8ae9c25 --- /dev/null +++ b/apps/sim/lib/knowledge/access/group-membership.ts @@ -0,0 +1,92 @@ +import { knowledgeExternalGroup, knowledgeExternalGroupMember } from '@sim/db/schema' +import { and, eq, gte, type SQL, sql } from 'drizzle-orm' +import { alias } from 'drizzle-orm/pg-core' +import type { ResourceScope } from '@/lib/core/resource-scope' +import { resourceScopeCondition } from '@/lib/core/resource-scope.server' +import { CONFLUENCE_SPACE_GROUP_PREFIX } from '@/lib/knowledge/access/confluence-space-groups' + +/** A directory exceeding these read-side budgets fails closed instead of truncating access. */ +export const MAX_EXTERNAL_GROUP_TOKENS = 100_000 +const MAX_EXTERNAL_GROUP_TOKEN_BYTES = 16 * 1024 * 1024 + +export function assertExternalGroupTokenCapacity(tokens: readonly string[]): void { + let bytes = 0 + for (const token of tokens) { + bytes += Buffer.byteLength(token, 'utf8') + if (bytes > MAX_EXTERNAL_GROUP_TOKEN_BYTES) { + throw new Error('External group access exceeded its token byte capacity') + } + } + if (tokens.length > MAX_EXTERNAL_GROUP_TOKENS) { + throw new Error('External group access exceeded its token capacity') + } +} + +/** Match a document's audience first, then the same reader directly or through one native group. */ +export function confluenceReaderGroupCondition(input: { + readerSubjectToken: SQL + cloudId: SQL + organizationId: SQL + workspaceId: SQL + freshEnough: SQL + hasToken: (token: SQL) => SQL +}): SQL { + const group = knowledgeExternalGroup + const member = knowledgeExternalGroupMember + const native = alias(group, 'confluence_native_group') + const nativeMember = alias(member, 'confluence_native_member') + const audienceMember = alias(member, 'confluence_audience_member') + const token = sql`('g:' || ${group.providerId} || ':' || ${group.tenantId} || ':' || ${group.externalGroupId})` + return sql` + EXISTS ( + SELECT 1 FROM ${group} + WHERE ${group.providerId} = 'confluence' + AND ${group.tenantId} = ${input.cloudId} + AND ${group.organizationId} IS NOT DISTINCT FROM ${input.organizationId} + AND ${group.workspaceId} IS NOT DISTINCT FROM ${input.workspaceId} + AND ${group.lastSyncedAt} >= ${input.freshEnough} + AND ${input.hasToken(token)} + AND ( + EXISTS ( + SELECT 1 FROM ${member} + WHERE ${member.groupId} = ${group.id} + AND ${member.subjectToken} = ${input.readerSubjectToken} + ) + OR (starts_with(${group.externalGroupId}, ${CONFLUENCE_SPACE_GROUP_PREFIX}) AND EXISTS ( + SELECT 1 FROM ${member} AS ${nativeMember} + INNER JOIN ${group} AS ${native} ON ${native.id} = ${nativeMember.groupId} + INNER JOIN ${member} AS ${audienceMember} ON ${audienceMember.groupId} = ${group.id} + AND ${audienceMember.subjectToken} = 'g:confluence:' || ${native.tenantId} || ':' || ${native.externalGroupId} + WHERE ${nativeMember.subjectToken} = ${input.readerSubjectToken} + AND ${native.providerId} = 'confluence' + AND ${native.tenantId} = ${input.cloudId} + AND ${native.organizationId} IS NOT DISTINCT FROM ${input.organizationId} + AND ${native.workspaceId} IS NOT DISTINCT FROM ${input.workspaceId} + AND ${native.lastSyncedAt} >= ${input.freshEnough} + AND NOT starts_with(${native.externalGroupId}, ${CONFLUENCE_SPACE_GROUP_PREFIX}) + )) + ) + ) + ` +} + +export function parentGroupTokensQuery( + tokens: readonly string[], + scope: ResourceScope, + freshEnough: Date +): SQL { + const group = knowledgeExternalGroup + const member = knowledgeExternalGroupMember + return sql` + SELECT DISTINCT 'g:' || ${group.providerId} || ':' || ${group.tenantId} || ':' || ${group.externalGroupId} AS token + FROM ${group} + INNER JOIN ${member} ON ${eq(member.groupId, group.id)} + WHERE ${and(resourceScopeCondition(group, scope), gte(group.lastSyncedAt, freshEnough))} + AND ${group.providerId} = 'confluence' + AND starts_with(${group.externalGroupId}, ${CONFLUENCE_SPACE_GROUP_PREFIX}) + AND starts_with(${member.subjectToken}, 'g:confluence:' || ${group.tenantId} || ':') + AND NOT starts_with(${member.subjectToken}, 'g:confluence:' || ${group.tenantId} || ':' || ${CONFLUENCE_SPACE_GROUP_PREFIX}) + AND ${member.subjectToken} IN (SELECT jsonb_array_elements_text(${JSON.stringify(tokens)}::text::jsonb)) + LIMIT ${MAX_EXTERNAL_GROUP_TOKENS + 1} + ` +} diff --git a/apps/sim/lib/knowledge/access/predicate.postgres.test.ts b/apps/sim/lib/knowledge/access/predicate.postgres.test.ts index fbd361b4aa8..14168ecf854 100644 --- a/apps/sim/lib/knowledge/access/predicate.postgres.test.ts +++ b/apps/sim/lib/knowledge/access/predicate.postgres.test.ts @@ -250,6 +250,28 @@ describe.runIf(Boolean(databaseUrl))('knowledge ACLs in PostgreSQL', () => { } const check = (grants?: ConfluenceSiteReadGrant[], userId = 'cf-reader', join = false) => readable([token], 'cf-document', join, undefined, userId, grants) + const audience = 'g:confluence:cloud-1:space-readers:123' + const engineering = 'g:confluence:cloud-1:engineering' + await connection.unsafe(` + INSERT INTO knowledge_external_group(id,organization_id,provider_id,tenant_id,external_group_id,last_synced_at) + VALUES ('cf-native','cf-org','confluence','cloud-1','engineering',now()), + ('cf-space','cf-org','confluence','cloud-1','space-readers:123',now()); + INSERT INTO knowledge_external_group_member(group_id,subject_token) + VALUES ('cf-native','${token}'), ('cf-space','${engineering}'); + UPDATE document SET acl=ARRAY['${audience}'], acl_requirements='[["${engineering}"],["${token}"]]' WHERE id='cf-document'; + `) + const nestedCheck = () => + readable([token, audience, engineering], 'cf-document', true, undefined, 'cf-reader', [grant]) + expect(await nestedCheck()).toBe(true) + await connection`UPDATE knowledge_external_group SET last_synced_at = now() - interval '2 days' WHERE id = 'cf-native'` + expect(await nestedCheck()).toBe(false) + await connection`UPDATE knowledge_external_group SET last_synced_at = now() WHERE id = 'cf-native'` + await connection`UPDATE knowledge_external_group_member SET subject_token = 's:confluence:-:cf-bob' WHERE group_id = 'cf-native'` + expect(await nestedCheck()).toBe(false) + await connection.unsafe(` + DELETE FROM knowledge_external_group WHERE id IN ('cf-native','cf-space'); + UPDATE document SET acl=ARRAY['${token}'], acl_requirements='[["${token}"]]' WHERE id='cf-document'; + `) for (const join of [false, true]) { expect(await check(undefined, 'cf-reader', join)).toBe(false) expect(await check([grant], 'cf-reader', join)).toBe(true) @@ -442,6 +464,16 @@ describe.runIf(Boolean(databaseUrl))('knowledge ACLs in PostgreSQL', () => { expect(await readable(groups, 'locked')).toBe(false) }) + it('reads safely with an audience larger than the SQL parameter limit', async () => { + const tokens = Array.from( + { length: 70_000 }, + (_, index) => `g:confluence:tenant:space-${index}` + ) + await putDocument('large-directory', [tokens[69_999]], [[tokens[50_000]]]) + expect(await readable(tokens, 'large-directory')).toBe(true) + expect(await readable(tokens.slice(0, 69_999), 'large-directory')).toBe(false) + }) + it('expires mirrored user, group and public grants, including legacy and orphaned source rows', async () => { for (const token of ['u:alice@corp.com', 'g:confluence:tenant:space', 'pub']) { await putDocument(token, [token]) diff --git a/apps/sim/lib/knowledge/access/predicate.ts b/apps/sim/lib/knowledge/access/predicate.ts index bbce2808f25..d3077014937 100644 --- a/apps/sim/lib/knowledge/access/predicate.ts +++ b/apps/sim/lib/knowledge/access/predicate.ts @@ -7,14 +7,13 @@ import { knowledgeConnector, knowledgeConnectorMember, knowledgeDocumentObservation, - knowledgeExternalGroup, - knowledgeExternalGroupMember, member, user, } from '@sim/db/schema' import { type SQL, sql } from 'drizzle-orm' import { EXTERNAL_GROUP_STALE_AFTER_MS } from '@/lib/knowledge/access/external-groups' import { SOURCE_ACL_MAX_AGE_MS } from '@/lib/knowledge/access/freshness' +import { confluenceReaderGroupCondition } from '@/lib/knowledge/access/group-membership' import type { KnowledgeAccessScope, SystemAccessScope } from '@/lib/knowledge/access/types' import { documentConnectorIsActive } from '@/lib/knowledge/documents/connector-lifecycle' import { searchIntegrationAccessCondition } from '@/lib/knowledge/search/integration-policy' @@ -23,17 +22,15 @@ import { ATLASSIAN_SERVICE_ACCOUNT_PROVIDER_ID } from '@/lib/oauth/types' /** Every Confluence clause must match the same confirmed reader, including that reader's groups. */ function confluenceReaderClause(hasToken: (token: SQL) => SQL): SQL { - return sql`(${hasToken(sql`confluence_read_grant.reader_subject_token`)} OR EXISTS ( - SELECT 1 FROM ${knowledgeExternalGroup} - JOIN ${knowledgeExternalGroupMember} ON ${knowledgeExternalGroupMember.groupId} = ${knowledgeExternalGroup.id} - WHERE ${knowledgeExternalGroupMember.subjectToken} = confluence_read_grant.reader_subject_token - AND ${knowledgeExternalGroup.providerId} = 'confluence' - AND ${knowledgeExternalGroup.tenantId} = confluence_read_grant.cloud_id - AND ${knowledgeExternalGroup.organizationId} IS NOT DISTINCT FROM ${knowledgeBase.organizationId} - AND ${knowledgeExternalGroup.workspaceId} IS NOT DISTINCT FROM ${knowledgeBase.workspaceId} - AND ${knowledgeExternalGroup.lastSyncedAt} >= statement_timestamp() - (${EXTERNAL_GROUP_STALE_AFTER_MS} * interval '1 millisecond') - AND ${hasToken(sql`('g:confluence:' || confluence_read_grant.cloud_id || ':' || ${knowledgeExternalGroup.externalGroupId})`)} - ))` + const groups = confluenceReaderGroupCondition({ + readerSubjectToken: sql`confluence_read_grant.reader_subject_token`, + cloudId: sql`confluence_read_grant.cloud_id`, + organizationId: sql`${knowledgeBase.organizationId}`, + workspaceId: sql`${knowledgeBase.workspaceId}`, + freshEnough: sql`statement_timestamp() - (${EXTERNAL_GROUP_STALE_AFTER_MS} * interval '1 millisecond')`, + hasToken, + }) + return sql`(${hasToken(sql`confluence_read_grant.reader_subject_token`)} OR ${groups})` } /** A cached space grant cannot substitute for the reader's current Confluence site access. */ @@ -168,11 +165,8 @@ function githubInstallationAccessCondition(scope: KnowledgeAccessScope): SQL { /** * The single read-side access predicate: the document's ACL overlaps the - * caller's token set. Tokens are bound as scalars and assembled with - * `ARRAY[...]` because the shared pool runs with `fetch_types: false`, under - * which a JS array bound as one parameter fails at execution (see - * packages/db/db.ts). A literal array also keeps the planner's statistics on - * `acl` usable, which is what lets it choose the GIN index for a selective set. + * caller's token set. Small token sets use literal arrays for GIN planning; + * large directories use one JSON parameter to avoid PostgreSQL's bind limit. * * Additional clauses preserve source intersections. Source-derived grants also * require recent evidence, independent of scheduler health. A drained member @@ -243,11 +237,13 @@ function storedKnowledgeAccessCondition( } /** - * A `text[]` literal assembled from scalar binds, for comparing against an - * ACL column. Every place that compares ACLs builds its array this way, for - * the `fetch_types: false` reason above. + * The pool uses fetch_types: false, so arrays must be constructed from scalar + * parameters. A JSON scalar keeps large sets below PostgreSQL's bind limit. */ export function textArrayLiteral(values: readonly string[]): SQL { + if (values.length > 1000) { + return sql`ARRAY(SELECT jsonb_array_elements_text(${JSON.stringify(values)}::text::jsonb))` + } return sql`ARRAY[${sql.join( values.map((value) => sql`${value}`), sql`, ` diff --git a/apps/sim/lib/knowledge/access/scope.test.ts b/apps/sim/lib/knowledge/access/scope.test.ts index ed6e30a11ea..9635a130a9f 100644 --- a/apps/sim/lib/knowledge/access/scope.test.ts +++ b/apps/sim/lib/knowledge/access/scope.test.ts @@ -53,6 +53,7 @@ vi.mock('@/lib/knowledge/access/connector-permissions', () => ({ loadConnectorPermissionGroupTokens: mockCsvGrants, })) +import { MAX_EXTERNAL_GROUP_TOKENS } from '@/lib/knowledge/access/group-membership' import { createKnowledgeAccessProvider, createUserKnowledgeAccessProvider, @@ -280,7 +281,8 @@ describe('createKnowledgeAccessProvider', () => { const [first, second] = await Promise.all([provider.get(), provider.get()]) expect(first).toBe(second) - expect(dbChainMockFns.select).toHaveBeenCalledTimes(2) + expect(dbChainMockFns.select).toHaveBeenCalledTimes(1) + expect(dbChainMockFns.selectDistinct).toHaveBeenCalledTimes(1) }) it('scopes live-source discovery to sources a held reader credential can prove', async () => { @@ -354,6 +356,34 @@ describe('tokens mirrored from a source directory', () => { queueTableRows(schemaMock.knowledgeExternalGroupMember, rows) } + it('fails closed on the direct-group overflow sentinel before discarding malformed tokens', async () => { + queueSubjects([ + { providerId: 'confluence', providerTenantId: null, providerSubjectId: 'reader' }, + ]) + queueGroups( + Array.from({ length: MAX_EXTERNAL_GROUP_TOKENS + 1 }, () => ({ + providerId: 'invalid:provider', + tenantId: 'cloud', + externalGroupId: 'group', + })) + ) + await expect(resolveKnowledgeAccessScope(SESSION, WORKSPACE)).rejects.toThrow('token capacity') + expect(dbChainMockFns.execute).not.toHaveBeenCalled() + }) + + it('fails closed on the audience overflow sentinel before merging duplicate tokens', async () => { + queueSubjects([ + { providerId: 'confluence', providerTenantId: null, providerSubjectId: 'reader' }, + ]) + queueGroups([{ providerId: 'confluence', tenantId: 'cloud', externalGroupId: 'group' }]) + dbChainMockFns.execute.mockResolvedValueOnce( + Array.from({ length: MAX_EXTERNAL_GROUP_TOKENS + 1 }, () => ({ + token: 'g:confluence:cloud:group', + })) + ) + await expect(resolveKnowledgeAccessScope(SESSION, WORKSPACE)).rejects.toThrow('token capacity') + }) + it('gives a person their own address and every group it belongs to', async () => { queueSubjects([ { diff --git a/apps/sim/lib/knowledge/access/scope.ts b/apps/sim/lib/knowledge/access/scope.ts index 2a534bc1760..b5120b36458 100644 --- a/apps/sim/lib/knowledge/access/scope.ts +++ b/apps/sim/lib/knowledge/access/scope.ts @@ -35,6 +35,11 @@ import { type GitHubReaderCredential, resolveGitHubInstallationReadGrants, } from '@/lib/knowledge/access/github-installation' +import { + assertExternalGroupTokenCapacity, + MAX_EXTERNAL_GROUP_TOKENS, + parentGroupTokensQuery, +} from '@/lib/knowledge/access/group-membership' import { confluenceSiteSourceCondition, githubInstallationSourceCondition, @@ -105,7 +110,7 @@ async function loadExternalGroupTokens( */ const freshEnough = new Date(Date.now() - EXTERNAL_GROUP_STALE_AFTER_MS) const rows = await db - .select({ + .selectDistinct({ providerId: knowledgeExternalGroup.providerId, tenantId: knowledgeExternalGroup.tenantId, externalGroupId: knowledgeExternalGroup.externalGroupId, @@ -122,6 +127,10 @@ async function loadExternalGroupTokens( gte(knowledgeExternalGroup.lastSyncedAt, freshEnough) ) ) + .limit(MAX_EXTERNAL_GROUP_TOKENS + 1) + if (rows.length > MAX_EXTERNAL_GROUP_TOKENS) { + throw new Error('External group access exceeded its token capacity') + } const tokens: string[] = [] for (const row of rows) { @@ -132,7 +141,19 @@ async function loadExternalGroupTokens( }) if (token) tokens.push(token) } - return tokens + assertExternalGroupTokenCapacity(tokens) + if (tokens.length > 0) { + const parents = await db.execute<{ token: string }>( + parentGroupTokensQuery(tokens, scope, freshEnough) + ) + if (parents.length > MAX_EXTERNAL_GROUP_TOKENS) { + throw new Error('External group access exceeded its token capacity') + } + for (const parent of parents) tokens.push(parent.token) + } + const uniqueTokens = sortAccessTokens(tokens) + assertExternalGroupTokenCapacity(uniqueTokens) + return uniqueTokens } export interface KnowledgeAccessScopeContext { diff --git a/apps/sim/lib/knowledge/access/tokens.test.ts b/apps/sim/lib/knowledge/access/tokens.test.ts index 13c7fbb2d0a..f9ca916d664 100644 --- a/apps/sim/lib/knowledge/access/tokens.test.ts +++ b/apps/sim/lib/knowledge/access/tokens.test.ts @@ -6,6 +6,7 @@ import { ACCESS_TOKEN_PATTERN, groupToken, isAccessToken, + isDirectoryMemberToken, isIdentityToken, MAX_ACL_TOKENS, sortAccessTokens, @@ -178,3 +179,44 @@ describe('directory identity tokens', () => { expect(isIdentityToken(token)).toBe(false) }) }) + +describe('nested directory memberships', () => { + const directory = { + providerId: 'confluence', + tenantId: 'cloud', + externalGroupId: 'space-readers:123', + } + it.each(['s:confluence:-:account', 'g:confluence:cloud:engineering'])( + 'accepts %s within the directory', + (value) => { + expect(isDirectoryMemberToken(value, directory)).toBe(true) + } + ) + it.each([ + 'g:jira:cloud:engineering', + 'g:confluence:other:engineering', + 'g:confluence:cloud:Engineering', + 'g:confluence:cloud: engineering ', + 'g:confluence:cloud:space-readers:456', + 'ws', + 'link', + 'pub', + ])('rejects %s', (value) => { + expect(isDirectoryMemberToken(value, directory)).toBe(false) + }) + + it('rejects nesting in native groups and other providers', () => { + expect( + isDirectoryMemberToken('g:confluence:cloud:engineering', { + ...directory, + externalGroupId: 'native', + }) + ).toBe(false) + expect( + isDirectoryMemberToken('g:google-drive:cloud:engineering', { + ...directory, + providerId: 'google-drive', + }) + ).toBe(false) + }) +}) diff --git a/apps/sim/lib/knowledge/access/tokens.ts b/apps/sim/lib/knowledge/access/tokens.ts index 5677283a584..59fc8e89f4c 100644 --- a/apps/sim/lib/knowledge/access/tokens.ts +++ b/apps/sim/lib/knowledge/access/tokens.ts @@ -1,4 +1,8 @@ import { normalizeEmail } from '@sim/utils/string' +import { + CONFLUENCE_SPACE_GROUP_PREFIX, + isConfluenceSpaceGroupId, +} from '@/lib/knowledge/access/confluence-space-groups' import { type MirroredDocumentAcl, WORKSPACE_ACCESS_TOKEN } from '@/lib/knowledge/access/types' /** @@ -86,13 +90,30 @@ export function isAccessToken(value: string): boolean { return ACCESS_TOKEN_PATTERN.test(value) } -/** Directory members are people; document audiences and groups cannot become member identities. */ +/** Identity seeds represent people; document audiences and groups are not reader identities. */ export function isIdentityToken(value: string): boolean { if (!isAccessToken(value)) return false if (value.startsWith('s:')) return true return value.startsWith('u:') && userToken(value.slice(2)) === value } +/** A Confluence space audience may reference native groups; native memberships remain identities. */ +export function isDirectoryMemberToken( + value: string, + directory: { providerId: string; tenantId: string; externalGroupId: string } +): boolean { + if (isIdentityToken(value)) return true + if (directory.providerId !== 'confluence' || !isConfluenceSpaceGroupId(directory.externalGroupId)) + return false + const prefix = `g:${directory.providerId}:${directory.tenantId || NO_TENANT_SEGMENT}:` + return ( + isAccessToken(value) && + value.startsWith(prefix) && + !value.slice(prefix.length).startsWith(CONFLUENCE_SPACE_GROUP_PREFIX) && + value === groupToken({ ...directory, groupId: value.slice(prefix.length) }) + ) +} + export interface SubjectCredential { providerId: string | null providerTenantId: string | null diff --git a/apps/sim/lib/knowledge/application/personal-source-setup.test.ts b/apps/sim/lib/knowledge/application/personal-source-setup.test.ts index 9ae1a409a4d..b40ff48ee5a 100644 --- a/apps/sim/lib/knowledge/application/personal-source-setup.test.ts +++ b/apps/sim/lib/knowledge/application/personal-source-setup.test.ts @@ -90,6 +90,46 @@ const runConnect = (changes = {}) => personalSourceSetup.execute({ principal, input: { ...connect, keys: ['PROJECT'], ...changes } }) describe('personal source setup', () => { + it.each(['confluence', 'jira'] as const)( + 'rejects mixed All and explicit %s keys before discovery or saving', + async (connectorType) => { + await expect(runConnect({ connectorType, keys: ['*', 'ENG'] })).rejects.toThrow( + 'Use "*" by itself for All, or remove it to select individual items.' + ) + expect(mocks.selector).not.toHaveBeenCalled() + expect(mocks.configure).not.toHaveBeenCalled() + } + ) + + it.each(['confluence', 'jira'] as const)( + 'authorizes %s All without freezing the available keys', + async (connectorType) => { + mocks.selector.mockResolvedValue({ kind: 'list', items: [], nextCursor: 'more' }) + await expect(runConnect({ connectorType, keys: ['*'] })).resolves.toMatchObject({ + kind: 'connected', + }) + expect(mocks.ownAccount).toHaveBeenCalled() + expect(mocks.selector).toHaveBeenCalledTimes(1) + expect(mocks.configure).toHaveBeenCalledWith( + expect.objectContaining({ + principal, + input: expect.objectContaining({ + sourceConfig: { + domain: credential.domain, + [connectorType === 'confluence' ? 'spaceKey' : 'projectKey']: '*', + }, + }), + }) + ) + } + ) + + it('does not let All bypass a failed site or credential check', async () => { + mocks.selector.mockRejectedValue(new Error('Site unavailable')) + await expect(runConnect({ keys: ['*'] })).rejects.toThrow('Site unavailable') + expect(mocks.configure).not.toHaveBeenCalled() + }) + afterEach(() => { vi.restoreAllMocks() }) diff --git a/apps/sim/lib/knowledge/application/personal-source-setup.ts b/apps/sim/lib/knowledge/application/personal-source-setup.ts index 407ad85b918..e38870f5103 100644 --- a/apps/sim/lib/knowledge/application/personal-source-setup.ts +++ b/apps/sim/lib/knowledge/application/personal-source-setup.ts @@ -28,6 +28,7 @@ import { MAX_SELECTOR_PAGES } from '@/lib/selectors/limits' import type { SelectorExecutionResult, SelectorRequest } from '@/lib/selectors/types' import { MAX_PERSONAL_SOURCE_SETUP_KEYS } from '@/lib/sim-search/personal-source-setup' import { CONNECTOR_META_REGISTRY } from '@/connectors/registry' +import { getSourceSelectionError, isAllSourceItems } from '@/connectors/selection' const logger = createLogger('PersonalSourceSetup') const VALIDATION_PHASE_TIMEOUT_MS = 30_000 @@ -177,6 +178,8 @@ export const personalSourceSetup = defineAuthorizedKnowledgeUseCase({ throw new OrchestrationError('validation', 'Select between 1 and 1,000 projects or spaces') } const keys = [...new Set(input.keys.map((key) => key.trim()))] + const selectionError = getSourceSelectionError(keys) + if (selectionError) throw new OrchestrationError('validation', selectionError) const remaining = new Set(keys) const cursors = new Set() const timeout = AbortSignal.timeout(VALIDATION_PHASE_TIMEOUT_MS) @@ -201,6 +204,10 @@ export const personalSourceSetup = defineAuthorizedKnowledgeUseCase({ throw error } if (result.kind !== 'list') throw new Error('Source discovery returned an unexpected result') + if (isAllSourceItems(keys)) { + remaining.clear() + break + } for (const option of result.items) remaining.delete(option.id) if (remaining.size === 0) break if (!result.nextCursor || result.truncated || cursors.has(result.nextCursor)) break diff --git a/apps/sim/lib/knowledge/connectors/external-group-sync.test.ts b/apps/sim/lib/knowledge/connectors/external-group-sync.test.ts index 85ee6179525..82082479983 100644 --- a/apps/sim/lib/knowledge/connectors/external-group-sync.test.ts +++ b/apps/sim/lib/knowledge/connectors/external-group-sync.test.ts @@ -93,6 +93,19 @@ describe('syncExternalDirectoryGroups', () => { expect(dir.listGroups).toHaveBeenCalledOnce() }) + it('rejects group references in a native directory membership', async () => { + const dir = directory({ + listGroupMembers: vi.fn(async (group) => ({ + group, + memberTokens: ['g:google-drive:corp.com:engineering'], + complete: true, + })), + }) + await expect( + syncExternalDirectoryGroups({ workspaceId: 'ws-1', directory: dir }) + ).rejects.toThrow('invalid identity token') + }) + it('replaces membership only from a complete enumeration, keeping the rest last-known-good', async () => { queueTableRows(schemaMock.knowledgeExternalGroup, []) const dir = directory({ diff --git a/apps/sim/lib/knowledge/connectors/external-group-sync.ts b/apps/sim/lib/knowledge/connectors/external-group-sync.ts index be56c0abe40..a5b24273f58 100644 --- a/apps/sim/lib/knowledge/connectors/external-group-sync.ts +++ b/apps/sim/lib/knowledge/connectors/external-group-sync.ts @@ -20,8 +20,9 @@ import { import { resourceScopeCondition } from '@/lib/core/resource-scope.server' import type { DbTransaction } from '@/lib/db/types' import { resolveKnowledgeAccessAvailability } from '@/lib/knowledge/access/availability' +import { CONFLUENCE_SPACE_GROUP_PREFIX } from '@/lib/knowledge/access/confluence-space-groups' import { EXTERNAL_GROUP_SYNC_INTERVAL_MS } from '@/lib/knowledge/access/external-groups' -import { canonicalGroupId, isIdentityToken } from '@/lib/knowledge/access/tokens' +import { canonicalGroupId, isDirectoryMemberToken } from '@/lib/knowledge/access/tokens' import { mirrorsSourceAcls } from '@/lib/knowledge/connectors/access-modes' import { resolveConnectorAccessToken, @@ -37,6 +38,7 @@ import { ConnectorDirectoryGroupAccessError, } from '@/connectors/source-error' import type { + ConnectorAclGroupMembership, ConnectorConfig, ConnectorDirectory, ConnectorDirectoryGroup, @@ -228,6 +230,7 @@ export async function syncExternalDirectoryGroups(input: { const groupId = await withDirectoryLease(lease, (tx) => upsertGroup({ ...owner, providerId, tenantId, group }, tx) ) + if (!groupId) throw new Error('Directory group could not be persisted') let membership: ConnectorDirectoryMembership try { membership = await directory.listGroupMembers(group) @@ -253,7 +256,12 @@ export async function syncExternalDirectoryGroups(input: { continue } await withDirectoryLease(lease, (tx) => - replaceGroupMembers(groupId, membership.memberTokens, tx) + replaceGroupMembers( + groupId, + membership.memberTokens, + { ...lease, externalGroupId: group.id }, + tx + ) ) refreshed += 1 } @@ -293,9 +301,10 @@ export async function syncExternalDirectoryGroups(input: { async function upsertGroup( input: DirectoryIdentity & { group: ConnectorDirectoryGroup }, - tx: DbTransaction -): Promise { - const { workspaceId, providerId, tenantId, group } = input + tx: DbTransaction, + observedAt?: Date +): Promise { + const { providerId, tenantId, group } = input const [row] = await tx .insert(knowledgeExternalGroup) .values({ @@ -315,18 +324,44 @@ async function upsertGroup( knowledgeExternalGroup.externalGroupId, ], set: { updatedAt: new Date() }, + ...(observedAt + ? { + setWhere: or( + isNull(knowledgeExternalGroup.lastSyncedAt), + lt(knowledgeExternalGroup.lastSyncedAt, observedAt) + ), + } + : {}), }) .returning({ id: knowledgeExternalGroup.id }) - return row.id + return row?.id +} + +/** The caller owns the transaction and fences it with its directory or content-sync lease. */ +export async function persistExternalGroupMembership( + input: DirectoryIdentity & ConnectorAclGroupMembership & { observedAt: Date }, + tx: DbTransaction +): Promise { + const groupId = await upsertGroup(input, tx, input.observedAt) + if (!groupId) return + await replaceGroupMembers( + groupId, + input.memberTokens, + { ...input, externalGroupId: input.group.id }, + tx, + input.observedAt + ) } -/** Membership replacement and its freshness watermark commit together under the directory lease. */ +/** Membership replacement and its freshness watermark commit together. */ async function replaceGroupMembers( groupId: string, memberTokens: string[], - tx: DbTransaction + directory: DirectoryIdentity & { externalGroupId: string }, + tx: DbTransaction, + observedAt?: Date ): Promise { - if (memberTokens.some((token) => !isIdentityToken(token))) { + if (memberTokens.some((token) => !isDirectoryMemberToken(token, directory))) { throw new Error('Directory membership contains an invalid identity token') } await tx @@ -339,7 +374,7 @@ async function replaceGroupMembers( } await tx .update(knowledgeExternalGroup) - .set({ lastSyncedAt: sql`clock_timestamp()`, updatedAt: sql`clock_timestamp()` }) + .set({ lastSyncedAt: observedAt ?? sql`clock_timestamp()`, updatedAt: sql`clock_timestamp()` }) .where(eq(knowledgeExternalGroup.id, groupId)) } @@ -358,6 +393,11 @@ async function pruneRemovedGroups(lease: DirectoryLease, keep: readonly string[] eq(knowledgeExternalGroup.tenantId, lease.tenantId), ...(keep.length > 0 ? [notInArray(knowledgeExternalGroup.externalGroupId, [...keep])] + : []), + ...(lease.providerId === 'confluence' + ? [ + sql`NOT starts_with(${knowledgeExternalGroup.externalGroupId}, ${CONFLUENCE_SPACE_GROUP_PREFIX})`, + ] : []) ) ) diff --git a/apps/sim/lib/knowledge/connectors/member-access.test.ts b/apps/sim/lib/knowledge/connectors/member-access.test.ts index a56745e6fe0..79821685197 100644 --- a/apps/sim/lib/knowledge/connectors/member-access.test.ts +++ b/apps/sim/lib/knowledge/connectors/member-access.test.ts @@ -74,6 +74,7 @@ import { ResourcePolicyNotFoundError, ResourcePolicyRevisionConflictError, } from '@/lib/resource-policies/repository' +import { confluenceConnectorMeta } from '@/connectors/confluence/meta' const GROUP_ID = 'group-1' const BINDING = { @@ -518,6 +519,34 @@ describe('knowledge connector member access', () => { ).toEqual({ ok: true, option: driveOption }) }) + it('keeps existing Confluence page credentials eligible without attachment access', () => { + const confluenceOption = { + ...driveOption, + id: 'option-confluence', + provider: 'confluence', + label: 'Confluence', + authorizationAppId: 'atlassian:app', + requiredScopes: [ + 'read:confluence-content.all', + 'read:page:confluence', + 'read:blogpost:confluence', + 'read:space:confluence', + 'read:label:confluence', + 'search:confluence', + 'offline_access', + ], + } + + expect( + validateKnowledgeConnectorMembersBinding({ + connectorMeta: confluenceConnectorMeta, + group: { status: 'active', options: [confluenceOption] }, + credentialGroupOptionId: confluenceOption.id, + sourceConfig: { domain: 'example.atlassian.net', spaceKey: ['ENG'], maxPages: '' }, + }) + ).toEqual({ ok: true, option: confluenceOption }) + }) + describe('a Slack option, whose members authorize through the workspace custom app', () => { const slackMeta = { name: 'Slack', diff --git a/apps/sim/lib/knowledge/connectors/sync-content-pass.test.ts b/apps/sim/lib/knowledge/connectors/sync-content-pass.test.ts index 5ab9a1ee5d1..6539a0dfe88 100644 --- a/apps/sim/lib/knowledge/connectors/sync-content-pass.test.ts +++ b/apps/sim/lib/knowledge/connectors/sync-content-pass.test.ts @@ -128,6 +128,7 @@ beforeEach(() => { version: { number: sourceVersion }, } if (url.pathname.endsWith('/spaces/space/pages')) return Response.json({ results: [page] }) + if (url.pathname.endsWith('/pages/page/attachments')) return Response.json({ results: [] }) if (url.pathname.endsWith('/pages/page')) { return Response.json({ ...page, @@ -421,6 +422,46 @@ describe('content pass checkpoint intent', () => { expect(result.docsUpdated).toBe(0) }) + it.each([403, 401])( + 'indexes healthy Confluence pages while preserving attachments after access failure %s', + async (status) => { + sourceBody = { value: '

    Current content

    ' } + const healthy = vi.mocked(fetch).getMockImplementation()! + vi.mocked(fetch).mockImplementation(async (input, init) => + String(input).includes('/pages/page/attachments') + ? status === 401 + ? Response.json( + { code: 401, message: 'Unauthorized; scope does not match' }, + { status } + ) + : new Response(null, { status }) + : healthy(input, init) + ) + const { pass, result } = await runPass({ access: 'admin' }) + expect(pass.complete).toBe(true) + expect(pass.checkpoint).toMatchObject({ + unsafe: true, + listingFailures: { + count: 1, + samples: [ + { + scope: 'page', + operation: 'confluence.attachments.list', + status, + reasons: [status === 401 ? 'attachment_scope_mismatch' : 'attachment_access_denied'], + }, + ], + }, + }) + expect(pass.holdNotice).toContain('unlisted documents were kept') + expect(result.docsAdded).toBe(1) + expect(result.docsDeleted).toBe(0) + expect(mocks.dispatch).toHaveBeenCalledOnce() + expect(mocks.hardDelete).not.toHaveBeenCalled() + expect(dbChainMockFns.set.mock.calls.some(([value]) => value.deletedAt != null)).toBe(false) + } + ) + it('does not reconcile deletions after a user listing failed, even without the unsafe marker', async () => { sourceBody = { value: '

    Current content

    ' } const checkpoint = { diff --git a/apps/sim/lib/knowledge/connectors/sync-engine.ts b/apps/sim/lib/knowledge/connectors/sync-engine.ts index c39b5524d43..6f2633b6168 100644 --- a/apps/sim/lib/knowledge/connectors/sync-engine.ts +++ b/apps/sim/lib/knowledge/connectors/sync-engine.ts @@ -18,6 +18,7 @@ import { import { withResourceOutboundScope } from '@/lib/core/network/resource-scope.server' import { resourceScopeFields, resourceScopeFromOwner } from '@/lib/core/resource-scope' import { EMPTY_ACL } from '@/lib/knowledge/access/tokens' +import type { MirroredDocumentAcl } from '@/lib/knowledge/access/types' import { CONTENT_ENGINE_ACCESS_MODES, type ContentEngineAccessMode, @@ -36,6 +37,7 @@ import { type DirectoryRefreshResult, directorySyncNotice, hasDirectorySyncNotice, + persistExternalGroupMembership, refreshMirroredDirectory, } from '@/lib/knowledge/connectors/external-group-sync' import { listingFingerprint } from '@/lib/knowledge/connectors/listing-checkpoint' @@ -116,6 +118,7 @@ export { */ async function applySourceMirroredAcls(input: { connectorId: string + kbOwner: KnowledgeBaseOwner connectorConfig: ConnectorConfig sourceConfig: Record syncContext: Record @@ -135,15 +138,36 @@ async function applySourceMirroredAcls(input: { * place until the next run. */ const unanswered = unansweredByListing(externalDocs) - const fetched = - unanswered.length > 0 && connectorConfig.getDocumentAcls - ? await connectorConfig.getDocumentAcls( - input.accessToken, - input.sourceConfig, - unanswered, - input.syncContext - ) - : {} + let fetched: Record = {} + if (unanswered.length > 0 && connectorConfig.getDocumentAcls) { + /** Audience freshness belongs to this observation, including when an old crawl resumes. */ + const [clock] = await db.execute<{ startedAt: string }>( + sql`SELECT statement_timestamp()::text AS "startedAt"` + ) + const observedAt = new Date(clock?.startedAt ?? '') + if (!Number.isFinite(observedAt.getTime())) + throw new Error('Could not read the sync database clock') + fetched = await connectorConfig.getDocumentAcls( + input.accessToken, + input.sourceConfig, + unanswered, + input.syncContext, + { + persistGroupMembership: (membership) => + db.transaction(async (tx) => { + if (input.lease) await assertSyncLeaseHeldInTx(tx, connectorId, input.lease) + await persistExternalGroupMembership( + { + ...resourceScopeFields(resourceScopeFromOwner(input.kbOwner)), + ...membership, + observedAt, + }, + tx + ) + }), + } + ) + } const { acls, unattributed, unresolvedExternalIds } = mergeMirroredAcls(externalDocs, fetched) const evidence = { unresolvedExternalIds, generationStartedAt: input.generationStartedAt } const listed = acls.size @@ -1158,6 +1182,7 @@ export async function executeSync( await directoryRefreshed return applySourceMirroredAcls({ connectorId, + kbOwner, connectorConfig, sourceConfig, syncContext, diff --git a/apps/sim/lib/knowledge/orchestration/connectors.test.ts b/apps/sim/lib/knowledge/orchestration/connectors.test.ts index 2a08a2bc246..e9606037f74 100644 --- a/apps/sim/lib/knowledge/orchestration/connectors.test.ts +++ b/apps/sim/lib/knowledge/orchestration/connectors.test.ts @@ -101,8 +101,23 @@ vi.mock('@/connectors/registry.server', () => ({ }, notion: { auth: { mode: 'apiKey', optional: true }, + configFields: [], validateConfig: vi.fn().mockResolvedValue({ valid: true }), }, + jira: { + name: 'Jira', + auth: { mode: 'oauth', provider: 'jira' }, + permissionScopedListing: { capFieldIds: [] }, + configFields: [ + { id: 'projectSelector', canonicalParamId: 'projectKey', selectAllValue: '*' }, + ], + }, + confluence: { + name: 'Confluence', + auth: { mode: 'oauth', provider: 'confluence' }, + permissionScopedListing: { capFieldIds: [] }, + configFields: [{ id: 'spaceSelector', canonicalParamId: 'spaceKey', selectAllValue: '*' }], + }, google_drive: { name: 'Google Drive', auth: { mode: 'oauth', provider: 'google-drive' }, @@ -155,6 +170,30 @@ describe('performCreateKnowledgeConnector', () => { resolveAccessToken: vi.fn(), } + it.each([ + { connectorType: 'jira', field: 'projectKey' }, + { connectorType: 'confluence', field: 'spaceKey' }, + ])( + 'rejects mixed All keys for credentialless $connectorType members', + async ({ connectorType, field }) => { + const outcome = await performCreateKnowledgeConnector({ + ...createParams, + connectorType, + sourceConfig: { domain: 'example.atlassian.net', [field]: ['*', 'ENG'] }, + accessMode: 'members', + membersBinding: { credentialGroupId: 'group-1', credentialGroupOptionId: 'option-1' }, + }) + expect(outcome).toMatchObject({ + success: false, + errorCode: 'validation', + error: 'Use "*" by itself for All, or remove it to select individual items.', + }) + expect(dbChainMockFns.insert).not.toHaveBeenCalled() + expect(mockGrant).not.toHaveBeenCalled() + expect(createParams.resolveAccessToken).not.toHaveBeenCalled() + } + ) + it('validates and encrypts a GitHub PAT without resolving an OAuth account or returning the secret', async () => { dbChainMockFns.limit.mockResolvedValueOnce([{ id: 'kb-1' }]) dbChainMockFns.returning.mockResolvedValueOnce([ @@ -481,6 +520,41 @@ describe('performUpdateKnowledgeConnector', () => { afterAll(resetDbChainMock) + it.each([ + { connectorType: 'jira', field: 'projectKey' }, + { connectorType: 'confluence', field: 'spaceKey' }, + ])( + 'rejects mixed All keys when editing credentialless $connectorType members', + async ({ connectorType, field }) => { + queueTableRows(schemaMock.knowledgeConnector, [ + { + id: 'conn-1', + connectorType, + accessMode: 'members', + status: 'active', + memberSyncStatus: 'idle', + credentialId: null, + }, + ]) + const validateSourceConfig = vi.fn() + const outcome = await performUpdateKnowledgeConnector({ + ...ACTOR, + knowledgeBase: KB, + connectorId: 'conn-1', + updates: { sourceConfig: { domain: 'example.atlassian.net', [field]: '*, ENG' } }, + resolveBillingAttribution, + validateSourceConfig, + }) + expect(outcome).toMatchObject({ + success: false, + errorCode: 'validation', + error: 'Use "*" by itself for All, or remove it to select individual items.', + }) + expect(dbChainMockFns.update).not.toHaveBeenCalled() + expect(validateSourceConfig).not.toHaveBeenCalled() + } + ) + it('rejects an update that names nothing before reading the connector', async () => { const outcome = await performUpdateKnowledgeConnector({ ...ACTOR, diff --git a/apps/sim/lib/knowledge/orchestration/connectors.ts b/apps/sim/lib/knowledge/orchestration/connectors.ts index f3ee6da4858..209d500ea68 100644 --- a/apps/sim/lib/knowledge/orchestration/connectors.ts +++ b/apps/sim/lib/knowledge/orchestration/connectors.ts @@ -64,6 +64,7 @@ import { createTagDefinition } from '@/lib/knowledge/tags/service' import { captureServerEvent } from '@/lib/posthog/server' import { searchSourceIdentity } from '@/lib/sim-search/source-identity' import { getConnectorApiKeyConfig } from '@/connectors/auth' +import { findSourceSelectionError } from '@/connectors/selection' import { PER_MEMBER_LISTING_CONTEXT } from '@/connectors/utils' const logger = createLogger('KnowledgeConnectorOrchestration') @@ -259,6 +260,9 @@ export async function performCreateKnowledgeConnector( return fail(`Unknown connector type: ${connectorType}`, 'validation') } + const selectionError = findSourceSelectionError(connectorConfig, sourceConfig) + if (selectionError) return fail(selectionError, 'validation') + try { await assertLiveSyncAllowed(resourceScopeFromOwner(kb), syncIntervalMinutes) } catch (error) { @@ -825,11 +829,13 @@ export async function performUpdateKnowledgeConnector( let nextSourceConfig = params.prepareSourceConfig ? await params.prepareSourceConfig(existing, updates.sourceConfig) : updates.sourceConfig - if (aclIsDerived(accessMode)) { - /** A derived-ACL mode has no listing cap; a save may refuse one, never store one. */ - const { CONNECTOR_REGISTRY } = await import('@/connectors/registry.server') - const connectorConfig = CONNECTOR_REGISTRY[existing.connectorType] - if (connectorConfig) { + const { CONNECTOR_REGISTRY } = await import('@/connectors/registry.server') + const connectorConfig = CONNECTOR_REGISTRY[existing.connectorType] + if (connectorConfig) { + const selectionError = findSourceSelectionError(connectorConfig, nextSourceConfig) + if (selectionError) return fail(selectionError, 'validation') + if (aclIsDerived(accessMode)) { + /** A derived-ACL mode has no listing cap; a save may refuse one, never store one. */ const capViolation = findListingCapViolation(connectorConfig, nextSourceConfig) if (capViolation) return fail(capViolation, 'validation') nextSourceConfig = stripListingCapFields(connectorConfig, nextSourceConfig) diff --git a/apps/sim/lib/sim-search/personal-source-setup.ts b/apps/sim/lib/sim-search/personal-source-setup.ts index 8afb1049c9f..7155fd26ed9 100644 --- a/apps/sim/lib/sim-search/personal-source-setup.ts +++ b/apps/sim/lib/sim-search/personal-source-setup.ts @@ -1,2 +1,2 @@ -/** Personal Atlassian setup stores an explicit, bounded snapshot of selected project or space keys. */ +/** Personal Atlassian setup bounds explicitly selected project or space keys. */ export const MAX_PERSONAL_SOURCE_SETUP_KEYS = 1000 diff --git a/apps/sim/lib/sim-search/source-identity.test.ts b/apps/sim/lib/sim-search/source-identity.test.ts index 67208256a0d..175202a5641 100644 --- a/apps/sim/lib/sim-search/source-identity.test.ts +++ b/apps/sim/lib/sim-search/source-identity.test.ts @@ -14,6 +14,18 @@ import { googleCalendarConnectorMeta } from '@/connectors/google-calendar/meta' import { googleDriveConnectorMeta } from '@/connectors/google-drive/meta' describe('Search source identity', () => { + it('describes dynamic All without exposing the persisted marker', () => { + expect( + describeSearchSource(confluenceConnectorMeta, { + domain: 'example.atlassian.net', + spaceKey: ['*'], + }) + ).toBe('example.atlassian.net · All') + expect(searchSourceIdentity(confluenceConnectorMeta, { spaceKey: ['*'] })).not.toBe( + searchSourceIdentity(confluenceConnectorMeta, { spaceKey: ['ENG', 'HR'] }) + ) + }) + it('normalizes multi-value settings and ignores runtime mappings and cleared caps', () => { expect( searchSourceIdentity(confluenceConnectorMeta, { diff --git a/apps/sim/lib/sim-search/source-identity.ts b/apps/sim/lib/sim-search/source-identity.ts index 5f6875948e8..2cba485f134 100644 --- a/apps/sim/lib/sim-search/source-identity.ts +++ b/apps/sim/lib/sim-search/source-identity.ts @@ -1,5 +1,6 @@ import { isPlainRecord } from '@sim/utils/object' import { truncate } from '@sim/utils/string' +import { isAllSourceItems } from '@/connectors/selection' import type { ConnectorMeta } from '@/connectors/types' import { parseMultiValue } from '@/connectors/utils' @@ -175,6 +176,7 @@ export function describeSearchSource( .flatMap((id) => { if (!SOURCE_ADDRESS_FIELDS.has(id) || caps.has(id)) return [] const value = sourceConfig[id] + if ((id === 'spaceKey' || id === 'projectKey') && isAllSourceItems(value)) return ['All'] if (labels[id]) return labels[id].map((option) => option.label) const values = parseMultiValue(value) const hasOpaqueId = values.some((item) => diff --git a/packages/db/schema.ts b/packages/db/schema.ts index 0be0f42f8d2..48db7dfb4b9 100644 --- a/packages/db/schema.ts +++ b/packages/db/schema.ts @@ -6037,8 +6037,8 @@ export const knowledgeExternalGroup = pgTable( /** * External group membership keyed by canonical identity tokens: verified * addresses (`u:`) or provider account identities (`s:`). Provider identities - * preserve permissions when a directory hides email addresses. Nested groups - * are flattened by directory sync, without requiring members to have Sim accounts. + * preserve permissions when a directory hides email addresses. Confluence space + * audiences may also reference native groups, whose members remain identities. */ export const knowledgeExternalGroupMember = pgTable( 'knowledge_external_group_member', From 1a7d66f61664fedb74fa8165301ff772f4c520ec Mon Sep 17 00:00:00 2001 From: Waleed Date: Thu, 17 Sep 2026 13:05:31 -0700 Subject: [PATCH 11/15] fix(oauth): refresh access tokens before expiry (#7939) --- apps/sim/lib/oauth/credential-service.test.ts | 237 ++++++++++++++++++ apps/sim/lib/oauth/credential-service.ts | 33 ++- 2 files changed, 259 insertions(+), 11 deletions(-) diff --git a/apps/sim/lib/oauth/credential-service.test.ts b/apps/sim/lib/oauth/credential-service.test.ts index cd388def9c3..cf88960c4d9 100644 --- a/apps/sim/lib/oauth/credential-service.test.ts +++ b/apps/sim/lib/oauth/credential-service.test.ts @@ -83,6 +83,9 @@ import { resolveServiceAccountToken, ServiceAccountTokenError, } from '@/lib/oauth/credential-service' +import { isInstagramProvider, shouldProactivelyRefreshInstagramToken } from '@/lib/oauth/instagram' +import { isMicrosoftProvider } from '@/lib/oauth/microsoft' +import { fanOutSlackTokenChain } from '@/lib/oauth/slack' import { GOOGLE_SERVICE_ACCOUNT_PROVIDER_ID } from '@/lib/oauth/types' const RAW_CREDENTIAL_ID = 'credential-raw-secret-id' @@ -340,6 +343,240 @@ describe('non-refreshable OAuth token expiry', () => { ) }) +describe('OAuth access-token refresh headroom', () => { + const now = new Date('2026-09-17T12:00:00.000Z') + + function createOAuthAccount(remainingMs: number | null = 6_000) { + return { + id: RAW_ACCOUNT_ID, + accountId: 'provider-subject', + providerId: 'google-drive', + userId: RAW_USER_ID, + accessToken: 'original-access-token', + refreshToken: 'original-refresh-token' as string | null, + accessTokenExpiresAt: remainingMs === null ? null : new Date(now.getTime() + remainingMs), + refreshTokenExpiresAt: null as Date | null, + updatedAt: new Date(now.getTime() - 30 * 24 * 60 * 60_000), + } + } + + type OAuthAccount = ReturnType + + function queueCredentialAccount(row: OAuthAccount) { + queueTableRows(credential, [ + { id: RAW_CREDENTIAL_ID, type: 'oauth', accountId: RAW_ACCOUNT_ID }, + ]) + queueTableRows(account, [row]) + } + + const readers = [ + { + name: 'credential bundle', + throwsOnFailure: false, + read: async (row: OAuthAccount) => { + queueCredentialAccount(row) + const bundle = await resolveCredentialTokenBundle(RAW_CREDENTIAL_ID, RAW_USER_ID, 'test') + return bundle?.accessToken ?? null + }, + }, + { + name: 'provider token', + throwsOnFailure: false, + read: async (row: OAuthAccount) => { + queueTableRows(account, [row]) + return getOAuthToken(RAW_USER_ID, row.providerId) + }, + }, + { + name: 'refresh result', + throwsOnFailure: true, + read: async (row: OAuthAccount) => + (await refreshTokenIfNeeded('test', row, RAW_ACCOUNT_ID)).accessToken, + }, + ] + + beforeEach(() => { + vi.clearAllMocks() + resetDbChainMock() + vi.useFakeTimers() + vi.setSystemTime(now) + vi.mocked(isInstagramProvider).mockReturnValue(false) + vi.mocked(shouldProactivelyRefreshInstagramToken).mockReturnValue(false) + vi.mocked(isMicrosoftProvider).mockReturnValue(false) + mocks.getRecentTerminalError.mockResolvedValue(null) + mocks.coalesceLocally.mockImplementation( + async (_key: string, producer: () => Promise) => producer() + ) + mocks.withLeaderLock.mockImplementation(async (options: { onLeader: () => Promise }) => + options.onLeader() + ) + mocks.refreshOAuthToken.mockResolvedValue({ + ok: true, + accessToken: 'refreshed-access-token', + refreshToken: 'rotated-refresh-token', + expiresIn: 3600, + }) + }) + + afterEach(() => { + vi.useRealTimers() + vi.mocked(isInstagramProvider).mockReturnValue(false) + vi.mocked(shouldProactivelyRefreshInstagramToken).mockReturnValue(false) + vi.mocked(isMicrosoftProvider).mockReturnValue(false) + }) + + describe.each(readers)('$name', ({ read, throwsOnFailure }) => { + it.each(['google-drive', 'jira', 'microsoft-teams'])( + 'refreshes %s with six seconds remaining before the first request', + async (providerId) => { + const row = { ...createOAuthAccount(), providerId } + await expect(read(row)).resolves.toBe('refreshed-access-token') + expect(mocks.refreshOAuthToken).toHaveBeenCalledExactlyOnceWith( + providerId, + 'original-refresh-token' + ) + expect(dbChainMockFns.set).toHaveBeenCalledWith( + expect.objectContaining({ + accessToken: 'refreshed-access-token', + refreshToken: 'rotated-refresh-token', + accessTokenExpiresAt: new Date(now.getTime() + 3_600_000), + }) + ) + } + ) + + it.each([ + { remainingMs: -1, refresh: true }, + { remainingMs: 0, refresh: true }, + { remainingMs: 300_000, refresh: true }, + { remainingMs: 300_001, refresh: false }, + { remainingMs: 3_600_000, refresh: false }, + { remainingMs: null, refresh: false }, + ])('handles $remainingMs milliseconds remaining', async ({ remainingMs, refresh }) => { + await expect(read(createOAuthAccount(remainingMs))).resolves.toBe( + refresh ? 'refreshed-access-token' : 'original-access-token' + ) + expect(mocks.refreshOAuthToken).toHaveBeenCalledTimes(refresh ? 1 : 0) + }) + + it('still refreshes a missing access token without a known expiry', async () => { + await expect(read({ ...createOAuthAccount(null), accessToken: '' })).resolves.toBe( + 'refreshed-access-token' + ) + }) + + it('preserves still-valid tokens without refresh capability', async () => { + await expect(read({ ...createOAuthAccount(), refreshToken: null })).resolves.toBe( + 'original-access-token' + ) + expect(mocks.refreshOAuthToken).not.toHaveBeenCalled() + }) + + it.each([6_000, -1])( + 'does not fall back to a token with %i milliseconds remaining when refresh fails', + async (remainingMs) => { + mocks.refreshOAuthToken.mockResolvedValue({ ok: false, errorCode: 'invalid_grant' }) + const result = read(createOAuthAccount(remainingMs)) + if (throwsOnFailure) await expect(result).rejects.toThrow('Failed to refresh token') + else await expect(result).resolves.toBeNull() + expect(mocks.refreshOAuthToken).toHaveBeenCalledTimes(1) + expect(dbChainMockFns.set).not.toHaveBeenCalled() + } + ) + + it('preserves Instagram proactive refresh and its healthy-token fallback', async () => { + vi.mocked(isInstagramProvider).mockReturnValue(true) + vi.mocked(shouldProactivelyRefreshInstagramToken).mockReturnValue(true) + mocks.refreshOAuthToken.mockResolvedValue({ ok: false, errorCode: 'temporarily_unavailable' }) + await expect( + read({ ...createOAuthAccount(3_600_000), providerId: 'instagram' }) + ).resolves.toBe('original-access-token') + expect(mocks.refreshOAuthToken).toHaveBeenCalledTimes(1) + }) + + it('does not bypass Instagram minimum token age with the generic refresh window', async () => { + vi.mocked(isInstagramProvider).mockReturnValue(true) + vi.mocked(shouldProactivelyRefreshInstagramToken).mockReturnValue(false) + await expect( + read({ ...createOAuthAccount(), providerId: 'instagram', updatedAt: now }) + ).resolves.toBe('original-access-token') + expect(shouldProactivelyRefreshInstagramToken).toHaveBeenCalledWith( + expect.objectContaining({ updatedAt: now }) + ) + expect(mocks.refreshOAuthToken).not.toHaveBeenCalled() + }) + }) + + it('waits for the refresh leader instead of accepting its near-expired stored token', async () => { + queueCredentialAccount(createOAuthAccount()) + queueTableRows(account, [createOAuthAccount()]) + queueTableRows(account, [{ ...createOAuthAccount(3_600_000), accessToken: 'leader-token' }]) + mocks.withLeaderLock.mockImplementation( + async (options: { onFollower: () => Promise }) => { + expect(await options.onFollower()).toBeNull() + return options.onFollower() + } + ) + await expect( + resolveCredentialTokenBundle(RAW_CREDENTIAL_ID, RAW_USER_ID, 'test') + ).resolves.toEqual({ accessToken: 'leader-token' }) + expect(mocks.refreshOAuthToken).not.toHaveBeenCalled() + }) + + it.each([ + { remainingMs: 6_000, refresh: true }, + { remainingMs: 300_000, refresh: true }, + { remainingMs: 300_001, refresh: false }, + ])( + 'applies headroom to the shared Slack token with $remainingMs left', + async ({ remainingMs, refresh }) => { + const row = { ...createOAuthAccount(-1), providerId: 'slack', accountId: 'TEXAMPLE-usr_U1' } + const chainVersion = new Date(0) + mocks.getFreshestSlackChain.mockResolvedValue({ + accessToken: 'installation-access-token', + refreshToken: 'installation-refresh-token', + accessTokenExpiresAt: new Date(now.getTime() + remainingMs), + chainVersion, + }) + queueCredentialAccount(row) + await expect( + resolveCredentialTokenBundle(RAW_CREDENTIAL_ID, RAW_USER_ID, 'test') + ).resolves.toEqual({ + accessToken: refresh ? 'refreshed-access-token' : 'installation-access-token', + }) + expect(mocks.refreshOAuthToken).toHaveBeenCalledTimes(refresh ? 1 : 0) + if (refresh) + expect(mocks.refreshOAuthToken).toHaveBeenCalledWith('slack', 'installation-refresh-token') + expect(fanOutSlackTokenChain).toHaveBeenCalledWith( + 'TEXAMPLE', + expect.objectContaining({ + accessToken: refresh ? 'refreshed-access-token' : 'installation-access-token', + }), + { ifChainUnchangedSince: chainVersion } + ) + } + ) + + it('preserves Microsoft refresh-token aging without rejecting a healthy access token', async () => { + vi.mocked(isMicrosoftProvider).mockReturnValue(true) + mocks.refreshOAuthToken.mockResolvedValue({ ok: false, errorCode: 'temporarily_unavailable' }) + const row = { + ...createOAuthAccount(3_600_000), + providerId: 'microsoft-teams', + refreshTokenExpiresAt: new Date(now.getTime() + 24 * 60 * 60_000), + } + queueCredentialAccount(row) + await expect( + resolveCredentialTokenBundle(RAW_CREDENTIAL_ID, RAW_USER_ID, 'test') + ).resolves.toEqual({ accessToken: 'original-access-token' }) + await expect(refreshTokenIfNeeded('test', row, RAW_ACCOUNT_ID)).resolves.toEqual({ + accessToken: 'original-access-token', + refreshed: false, + }) + expect(mocks.refreshOAuthToken).toHaveBeenCalledTimes(2) + }) +}) + describe('Google service-account token minting', () => { const { privateKey, publicKey } = generateKeyPairSync('rsa', { modulusLength: 2048 }) const fetchMock = vi.fn() diff --git a/apps/sim/lib/oauth/credential-service.ts b/apps/sim/lib/oauth/credential-service.ts index ebfb51a8118..d14c4d3921a 100644 --- a/apps/sim/lib/oauth/credential-service.ts +++ b/apps/sim/lib/oauth/credential-service.ts @@ -63,6 +63,7 @@ import { } from '@/lib/slack-search/app-configuration' const logger = createLogger('OAuthCredentialService') +const OAUTH_ACCESS_TOKEN_REFRESH_WINDOW_MS = 5 * 60 * 1000 export interface CredentialTokenResolutionOptions { /** @@ -857,6 +858,19 @@ interface CoalescedRefreshOptions { privacyMode?: 'selector' } +/** + * Leave time for request preparation and transit, including when reusing another worker's token. + * Instagram instead uses its age-gated long-lived token refresh policy. + */ +function isOAuthAccessTokenExpiring( + expiresAt: Date | null | undefined, + providerId: string, + now = new Date() +): boolean { + const refreshWindowMs = isInstagramProvider(providerId) ? 0 : OAUTH_ACCESS_TOKEN_REFRESH_WINDOW_MS + return expiresAt != null && expiresAt.getTime() <= now.getTime() + refreshWindowMs +} + /** * Slack lock budgets sized past `TOKEN_REFRESH_TIMEOUT_MS` (15s) in * lib/oauth/oauth.ts: installation-keyed locks make every sibling row's request @@ -932,7 +946,7 @@ async function performCoalescedRefresh({ if ( freshest.accessToken && freshest.accessTokenExpiresAt && - freshest.accessTokenExpiresAt > new Date() + !isOAuthAccessTokenExpiring(freshest.accessTokenExpiresAt, providerId) ) { await fanOutSlackTokenChain( slackTeamId, @@ -1039,7 +1053,7 @@ async function performCoalescedRefresh({ if ( row?.accessToken && row.accessTokenExpiresAt && - row.accessTokenExpiresAt > new Date() + !isOAuthAccessTokenExpiring(row.accessTokenExpiresAt, providerId) ) { logger.info('Got fresh access token from coalesced refresh', logContext) return row.accessToken @@ -1092,8 +1106,6 @@ export async function getOAuthToken(userId: string, providerId: string): Promise const credential = connections[0] - // Determine whether we should refresh: missing/expired token, or Instagram - // long-lived token nearing expiry (Meta cannot refresh after expiry). const now = new Date() const tokenExpiry = credential.accessTokenExpiresAt if (!credential.refreshToken && tokenExpiry && tokenExpiry <= now) { @@ -1101,7 +1113,8 @@ export async function getOAuthToken(userId: string, providerId: string): Promise return null } const accessTokenNeedsRefresh = - !!credential.refreshToken && (!credential.accessToken || (tokenExpiry && tokenExpiry < now)) + !!credential.refreshToken && + (!credential.accessToken || isOAuthAccessTokenExpiring(tokenExpiry, providerId, now)) const instagramNeedsProactiveRefresh = !!credential.refreshToken && isInstagramProvider(providerId) && @@ -1178,7 +1191,6 @@ export async function resolveCredentialTokenBundle( return null } - // Decide if we should refresh: token missing OR expired const accessTokenExpiresAt = credential.accessTokenExpiresAt const refreshTokenExpiresAt = credential.refreshTokenExpiresAt const now = new Date() @@ -1188,10 +1200,10 @@ export async function resolveCredentialTokenBundle( return null } - // Check if access token needs refresh (missing or expired) const accessTokenNeedsRefresh = !!credential.refreshToken && - (!credential.accessToken || (accessTokenExpiresAt && accessTokenExpiresAt <= now)) + (!credential.accessToken || + isOAuthAccessTokenExpiring(accessTokenExpiresAt, credential.providerId, now)) // Check if we should proactively refresh to prevent refresh token expiry // This applies to Microsoft providers whose refresh tokens expire after 90 days of inactivity @@ -1292,7 +1304,6 @@ export async function refreshTokenIfNeeded( ): Promise<{ accessToken: string; refreshed: boolean }> { const resolvedCredentialId = credential.resolvedCredentialId ?? credentialId - // Decide if we should refresh: token missing OR expired const accessTokenExpiresAt = credential.accessTokenExpiresAt const refreshTokenExpiresAt = credential.refreshTokenExpiresAt const now = new Date() @@ -1301,10 +1312,10 @@ export async function refreshTokenIfNeeded( throw new Error('OAuth access token expired and cannot be refreshed; reconnect the account') } - // Check if access token needs refresh (missing or expired) const accessTokenNeedsRefresh = !!credential.refreshToken && - (!credential.accessToken || (accessTokenExpiresAt && accessTokenExpiresAt <= now)) + (!credential.accessToken || + isOAuthAccessTokenExpiring(accessTokenExpiresAt, credential.providerId, now)) // Check if we should proactively refresh to prevent refresh token expiry // This applies to Microsoft providers whose refresh tokens expire after 90 days of inactivity From 8508ba18d0516cc63802515f31c3af121e288dac Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Thu, 17 Sep 2026 13:14:53 -0700 Subject: [PATCH 12/15] fix(file-search): bound dispatcher claims and skip locked rows (#7940) --- .../search/dispatcher.integration.ts | 70 ++++++++++++++++++ .../lib/workspace-files/search/dispatcher.ts | 72 ++++++++----------- 2 files changed, 100 insertions(+), 42 deletions(-) diff --git a/apps/sim/lib/workspace-files/search/dispatcher.integration.ts b/apps/sim/lib/workspace-files/search/dispatcher.integration.ts index 2a9eadf2599..afc6eee134c 100644 --- a/apps/sim/lib/workspace-files/search/dispatcher.integration.ts +++ b/apps/sim/lib/workspace-files/search/dispatcher.integration.ts @@ -61,6 +61,11 @@ describe('workspace file search dispatch PostgreSQL deadlines', () => { workspace_id text PRIMARY KEY, enqueued_at timestamp NOT NULL, updated_at timestamp NOT NULL, last_dispatched_at timestamp )` + await connection`CREATE INDEX ON workspace_file_search_index + (workspace_id, updated_at, file_id, source_content_updated_at) + WHERE status = 'pending' AND dispatched_at IS NULL` + await connection`CREATE INDEX ON workspace_file_search_index (workspace_id, dispatched_at) + WHERE status = 'pending' AND dispatched_at IS NOT NULL` await connection`INSERT INTO workspace_file_search_backfill (id, updated_at) VALUES ('workspace-file-search-v1', '2026-09-16 00:00:00')` database.current = drizzle(connection) @@ -92,6 +97,71 @@ describe('workspace file search dispatch PostgreSQL deadlines', () => { }) } + async function seedQueue(workspaceId: string, queued: number, active = 0) { + await connection`UPDATE workspace_file_search_backfill SET completed_at = now()` + await connection`INSERT INTO workspace_files (id, workspace_id, context, content_updated_at) + SELECT ${workspaceId} || '-' || lpad(n::text, 6, '0'), ${workspaceId}, 'workspace', '2026-09-16' + FROM generate_series(1, ${queued + active}) n` + await connection`INSERT INTO workspace_file_search_index + (file_id, workspace_id, source_content_updated_at, status, updated_at, dispatched_at) + SELECT id, workspace_id, content_updated_at, 'pending', '2026-09-16', + CASE WHEN row_number() OVER (ORDER BY id DESC) <= ${active} THEN now() ELSE NULL END + FROM workspace_files WHERE workspace_id = ${workspaceId}` + await connection`INSERT INTO workspace_file_search_dispatch_queue + (workspace_id, enqueued_at, updated_at) VALUES (${workspaceId}, now(), now())` + } + + it('skips a locked candidate without losing it or exceeding workspace capacity', async () => { + await seedQueue('workspace-1', 3, 1) + await connection.begin(async (tx) => { + await tx`SELECT file_id FROM workspace_file_search_index + WHERE file_id = 'workspace-1-000001' FOR UPDATE` + const results = await Promise.all([ + prepareWorkspaceFileSearchDispatch(), + prepareWorkspaceFileSearchDispatch(), + ]) + expect(results.flatMap((result) => result.payloads).map((payload) => payload.fileId)).toEqual( + ['workspace-1-000002'] + ) + const [locked] = await tx`SELECT dispatched_at FROM workspace_file_search_index + WHERE file_id = 'workspace-1-000001'` + expect(locked.dispatched_at).toBeNull() + }) + expect((await prepareWorkspaceFileSearchDispatch()).payloads).toEqual([]) + await connection`UPDATE workspace_file_search_index SET status = 'ready' + WHERE file_id = 'workspace-1-000002'` + const retry = await prepareWorkspaceFileSearchDispatch() + expect(retry.payloads.map((payload) => payload.fileId)).toEqual(['workspace-1-000001']) + }) + + it('claims only available slots from a large backlog and preserves current-file eligibility', async () => { + await seedQueue('workspace-1', 10_000, 1) + await seedQueue('workspace-2', 3) + await connection`UPDATE workspace_files SET deleted_at = now() WHERE id = 'workspace-1-000001'` + await connection`UPDATE workspace_files SET content_updated_at = '2026-09-17' + WHERE id = 'workspace-1-000002'` + await connection`UPDATE workspace_files SET context = 'execution' WHERE id = 'workspace-1-000003'` + const result = await prepareWorkspaceFileSearchDispatch() + expect(result.payloads.map((payload) => payload.fileId).sort()).toEqual([ + 'workspace-1-000004', + 'workspace-2-000001', + 'workspace-2-000002', + ]) + expect((await prepareWorkspaceFileSearchDispatch()).payloads).toEqual([]) + }) + + it('honors the remaining global capacity across workspace probes', async () => { + await seedQueue('workspace-1', 3) + await seedQueue('workspace-2', 3) + await seedQueue('workspace-active', 0, 99) + const result = await prepareWorkspaceFileSearchDispatch() + expect(result.payloads).toHaveLength(1) + expect((await prepareWorkspaceFileSearchDispatch()).payloads).toEqual([]) + const [row] = await connection`SELECT count(*)::int AS active FROM workspace_file_search_index + WHERE status = 'pending' AND dispatched_at IS NOT NULL` + expect(row.active).toBe(100) + }) + it('fails on a locked backfill row and releases the dispatcher lock', async () => { let release = () => {} let locked = () => {} diff --git a/apps/sim/lib/workspace-files/search/dispatcher.ts b/apps/sim/lib/workspace-files/search/dispatcher.ts index 91f4b6d1899..a9346754a62 100644 --- a/apps/sim/lib/workspace-files/search/dispatcher.ts +++ b/apps/sim/lib/workspace-files/search/dispatcher.ts @@ -301,6 +301,7 @@ async function reapStaleClaims(tx: DbTransaction, now: Date): Promise { return rows.length } +/** Probe each workspace's available slots and lock candidates before the update, skipping busy rows. */ async function claimQueuedWorkspaceJobs( tx: DbTransaction, workspaceIds: readonly string[], @@ -320,48 +321,35 @@ async function claimQueuedWorkspaceJobs( WITH selected_workspace(workspace_id) AS ( VALUES ${workspaceValues} ), - workspace_active AS ( - SELECT search_index.workspace_id, count(*)::int AS active_count - FROM workspace_file_search_index AS search_index - INNER JOIN selected_workspace AS selected - ON selected.workspace_id = search_index.workspace_id - WHERE search_index.status = 'pending' - AND search_index.dispatched_at IS NOT NULL - GROUP BY search_index.workspace_id - ), - ranked AS ( - SELECT - search_index.workspace_id, - search_index.file_id, - search_index.source_content_updated_at, - search_index.updated_at, - coalesce(workspace_active.active_count, 0) AS active_count, - row_number() OVER ( - PARTITION BY search_index.workspace_id - ORDER BY - search_index.updated_at, - search_index.file_id, - search_index.source_content_updated_at - ) AS workspace_rank - FROM workspace_file_search_index AS search_index - INNER JOIN selected_workspace AS selected - ON selected.workspace_id = search_index.workspace_id - INNER JOIN workspace_files AS file - ON file.id = search_index.file_id - AND file.workspace_id = search_index.workspace_id - AND file.context = 'workspace' - AND file.deleted_at IS NULL - AND file.content_updated_at = search_index.source_content_updated_at - LEFT JOIN workspace_active - ON workspace_active.workspace_id = search_index.workspace_id - WHERE search_index.status = 'pending' - AND search_index.dispatched_at IS NULL - ), - candidates AS ( - SELECT workspace_id, file_id, source_content_updated_at - FROM ranked - WHERE workspace_rank <= ${FILE_SEARCH_INDEX_WORKSPACE_OUTSTANDING} - active_count - ORDER BY updated_at, workspace_id, file_id, source_content_updated_at + candidates AS MATERIALIZED ( + SELECT queued.* + FROM selected_workspace AS selected + CROSS JOIN LATERAL ( + SELECT count(*)::int AS active_count + FROM ( + SELECT 1 FROM workspace_file_search_index AS active + WHERE active.workspace_id = selected.workspace_id + AND active.status = 'pending' AND active.dispatched_at IS NOT NULL + LIMIT ${FILE_SEARCH_INDEX_WORKSPACE_OUTSTANDING} + ) AS active_claims + ) AS workspace_active + CROSS JOIN LATERAL ( + SELECT search_index.workspace_id, search_index.file_id, + search_index.source_content_updated_at, search_index.updated_at + FROM workspace_file_search_index AS search_index + INNER JOIN workspace_files AS file + ON file.id = search_index.file_id + AND file.workspace_id = search_index.workspace_id + AND file.context = 'workspace' + AND file.deleted_at IS NULL + AND file.content_updated_at = search_index.source_content_updated_at + WHERE search_index.workspace_id = selected.workspace_id + AND search_index.status = 'pending' AND search_index.dispatched_at IS NULL + ORDER BY search_index.updated_at, search_index.file_id, search_index.source_content_updated_at + LIMIT greatest(0, ${FILE_SEARCH_INDEX_WORKSPACE_OUTSTANDING} - workspace_active.active_count) + FOR UPDATE OF search_index SKIP LOCKED + ) AS queued + ORDER BY queued.updated_at, queued.workspace_id, queued.file_id, queued.source_content_updated_at LIMIT ${remainingGlobalCapacity} ) UPDATE workspace_file_search_index AS search_index From 21129987ec77e607d38bcc57b389a686c46e21e6 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Thu, 17 Sep 2026 13:53:29 -0700 Subject: [PATCH 13/15] fix(outbox): avoid backlog scans during event discovery (#7942) --- apps/sim/lib/core/outbox/queries.ts | 44 + .../lib/core/outbox/service.integration.ts | 118 +- apps/sim/lib/core/outbox/service.test.ts | 76 +- apps/sim/lib/core/outbox/service.ts | 113 +- .../db/migrations/0357_outbox_autovacuum.sql | 4 + .../db/migrations/meta/0357_snapshot.json | 27509 ++++++++++++++++ packages/db/migrations/meta/_journal.json | 7 + 7 files changed, 27811 insertions(+), 60 deletions(-) create mode 100644 apps/sim/lib/core/outbox/queries.ts create mode 100644 packages/db/migrations/0357_outbox_autovacuum.sql create mode 100644 packages/db/migrations/meta/0357_snapshot.json diff --git a/apps/sim/lib/core/outbox/queries.ts b/apps/sim/lib/core/outbox/queries.ts new file mode 100644 index 00000000000..6176e169468 --- /dev/null +++ b/apps/sim/lib/core/outbox/queries.ts @@ -0,0 +1,44 @@ +import { outboxEvent } from '@sim/db/schema' +import { sql } from 'drizzle-orm' + +const MAX_READY_EVENT_TYPES = 128 + +/** + * Walks the pending index one type at a time, reading only its earliest availability. + * The time filter belongs AFTER the walk: filtering inside each seek would scan + * all future rows of a type with no ready events. A strictly increasing type ends + * the recursion, including unknown types left by rolling deployments. + * + * Sort and cap ready heads after discovery so future types cannot hide ready ones, + * and preserve the scheduler's oldest-first ordering. Database work scales with + * distinct pending types (plus MVCC visibility checks), not their event counts; + * at most 128 metadata rows cross into the worker, with no payloads or fan-out. + */ +export function readyEventTypesQuery(now: Date) { + return sql` + WITH RECURSIVE pending_heads AS ( + ( + SELECT event_type, available_at + FROM ${outboxEvent} + WHERE status = 'pending' + ORDER BY event_type, available_at + LIMIT 1 + ) + UNION ALL + SELECT next_head.event_type, next_head.available_at + FROM pending_heads + CROSS JOIN LATERAL ( + SELECT event_type, available_at + FROM ${outboxEvent} + WHERE status = 'pending' AND event_type > pending_heads.event_type + ORDER BY event_type, available_at + LIMIT 1 + ) next_head + ) + SELECT event_type AS "eventType" + FROM pending_heads + WHERE available_at <= ${now.toISOString()}::timestamp + ORDER BY available_at, event_type + LIMIT ${MAX_READY_EVENT_TYPES} + ` +} diff --git a/apps/sim/lib/core/outbox/service.integration.ts b/apps/sim/lib/core/outbox/service.integration.ts index 8aaf42ff375..bb9dc62e3dd 100644 --- a/apps/sim/lib/core/outbox/service.integration.ts +++ b/apps/sim/lib/core/outbox/service.integration.ts @@ -17,6 +17,7 @@ vi.mock('@sim/db', () => ({ }, })) +import { readyEventTypesQuery } from '@/lib/core/outbox/queries' import { type OutboxHandler, processOutboxEvents, @@ -26,6 +27,8 @@ import { interface QueryPlan { 'Node Type': string 'Index Name'?: string + 'Shared Hit Blocks': number + 'Shared Read Blocks': number Plans?: QueryPlan[] } @@ -90,10 +93,10 @@ describe('outbox scheduling in PostgreSQL', () => { async function seedBacklog( eventType: string, count: number, - status: 'pending' | 'completed' | 'processing' + status: 'pending' | 'completed' | 'processing', + availableAt = new Date(Date.now() - 60_000) ) { const prefix = generateId() - const availableAt = new Date(Date.now() - 60_000) const createdAt = new Date(Date.now() - 24 * 60 * 60_000) const lockedAt = status === 'processing' ? new Date(Date.now() - 11 * 60_000) : null eventTypes.add(eventType) @@ -110,6 +113,114 @@ describe('outbox scheduling in PostgreSQL', () => { } } + async function expectBoundedDiscovery(now: Date) { + const plans = await db.execute<{ 'QUERY PLAN': { Plan: QueryPlan }[] }>(sql` + EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) ${readyEventTypesQuery(now)} + `) + const plan = plans[0]['QUERY PLAN'][0].Plan + const nodes = planNodes(plan) + expect(nodes.some((node) => node['Node Type'] === 'Recursive Union')).toBe(true) + expect(nodes.some((node) => node['Node Type'] === 'Seq Scan')).toBe(false) + /** Buffer work, unlike wall-clock time, catches a backlog scan even on a warm local database. */ + expect(plan['Shared Hit Blocks'] + plan['Shared Read Blocks']).toBeLessThan(1_000) + } + + it('discovers an empty queue without returning a null type', async () => { + expect(await db.execute(readyEventTypesQuery(new Date()))).toEqual([]) + }) + + it('uses earliest availability, inclusive deadlines, and deterministic type ties', async () => { + const now = new Date('2026-01-01T12:00:00.000Z') + const fixtures = [ + { eventType: 'test.outbox.z-first', availableAt: new Date(now.getTime() - 1) }, + { eventType: 'test.outbox.z-first', availableAt: new Date(now.getTime() + 60_000) }, + { eventType: 'test.outbox.b-tie', availableAt: now }, + { eventType: 'test.outbox.a-tie', availableAt: now }, + { eventType: 'test.outbox.future', availableAt: new Date(now.getTime() + 1) }, + { eventType: 'test.outbox.completed', availableAt: now, status: 'completed' }, + { eventType: 'test.outbox.processing', availableAt: now, status: 'processing' }, + { eventType: 'test.outbox.dead', availableAt: now, status: 'dead_letter' }, + ] + for (const fixture of fixtures) eventTypes.add(fixture.eventType) + await db + .insert(outboxEvent) + .values(fixtures.map((row) => ({ id: generateId(), payload: {}, ...row }))) + + expect(await db.execute(readyEventTypesQuery(now))).toEqual([ + { eventType: 'test.outbox.z-first' }, + { eventType: 'test.outbox.a-tie' }, + { eventType: 'test.outbox.b-tie' }, + ]) + }) + + it('caps ready types after ordering all heads, including types unknown to this worker', async () => { + const now = new Date() + const rows = Array.from({ length: 140 }, (_, index) => ({ + id: generateId(), + eventType: `test.outbox.type-${String(index).padStart(3, '0')}`, + payload: {}, + availableAt: new Date(now.getTime() - index - 1), + })) + for (const row of rows) eventTypes.add(row.eventType) + await db.insert(outboxEvent).values(rows) + + expect(await db.execute(readyEventTypesQuery(now))).toEqual( + [...rows] + .reverse() + .slice(0, 128) + .map(({ eventType }) => ({ eventType })) + ) + }) + + it('skips large future backlogs and more than 128 future types without hiding ready work', async () => { + const future = new Date(Date.now() + 48 * 60 * 60_000) + await seedBacklog('test.outbox.a-expiry', 100_000, 'pending', future) + const futureTypes = Array.from({ length: 130 }, (_, index) => ({ + id: generateId(), + eventType: `test.outbox.future-${index}`, + payload: {}, + availableAt: future, + })) + for (const row of futureTypes) eventTypes.add(row.eventType) + await db.insert(outboxEvent).values(futureTypes) + await enqueue('test.outbox.z-ready', 1) + await connection`VACUUM (ANALYZE) outbox_event` + + const now = new Date() + expect(await db.execute(readyEventTypesQuery(now))).toEqual([ + { eventType: 'test.outbox.z-ready' }, + ]) + await expectBoundedDiscovery(now) + expect(await processOutboxEvents({ 'test.outbox.z-ready': async () => {} })).toMatchObject({ + processed: 1, + }) + expect(await db.execute(readyEventTypesQuery(new Date()))).toEqual([]) + }, 60_000) + + it('retains bounded retries for a type missing during a rolling deployment', async () => { + const [event] = await enqueue('test.outbox.unknown', 1) + + expect(await processOutboxEvents({})).toMatchObject({ retried: 1 }) + const [pending] = await db.select().from(outboxEvent).where(eq(outboxEvent.id, event.id)) + expect(pending).toMatchObject({ status: 'pending', attempts: 1 }) + expect(pending.availableAt.getTime()).toBeGreaterThan(Date.now()) + }) + + it('lets claims skip a locked head without hiding other rows of that type', async () => { + const [locked, available] = await enqueue('test.outbox.locked', 2) + const delivered: string[] = [] + await connection.begin(async (transaction) => { + await transaction`SELECT id FROM outbox_event WHERE id = ${locked.id} FOR UPDATE` + const result = await processOutboxEvents({ + 'test.outbox.locked': async (_payload, context) => { + delivered.push(context.eventId) + }, + }) + expect(result.processed).toBe(1) + }) + expect(delivered).toEqual([available.id]) + }) + it('serves newer event types before exhausting an older cleanup backlog', async () => { await enqueue('test.outbox.cleanup', 1_000) const [dispatch] = await enqueue('test.outbox.dispatch', 1, 2_000) @@ -191,7 +302,8 @@ describe('outbox scheduling in PostgreSQL', () => { await seedBacklog('test.outbox.cleanup', 100_000, 'pending') const [dispatch] = await enqueue('test.outbox.dispatch', 1) const [billing] = await enqueue('test.outbox.billing', 1) - await db.execute(sql`ANALYZE outbox_event`) + await connection`VACUUM (ANALYZE) outbox_event` + await expectBoundedDiscovery(new Date()) const plans = await db.execute<{ 'QUERY PLAN': { Plan: QueryPlan }[] }>(sql` EXPLAIN (FORMAT JSON) diff --git a/apps/sim/lib/core/outbox/service.test.ts b/apps/sim/lib/core/outbox/service.test.ts index f0fa27afdea..6f3e1bade53 100644 --- a/apps/sim/lib/core/outbox/service.test.ts +++ b/apps/sim/lib/core/outbox/service.test.ts @@ -3,6 +3,7 @@ */ import { outboxEvent } from '@sim/db/schema' +import { createLogger } from '@sim/logger' import { dbChainMock, dbChainMockFns, queueTableRows, resetDbChainMock } from '@sim/testing' import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vitest' @@ -36,6 +37,11 @@ import { withOutboxHandlerTimeout, } from '@/lib/core/outbox/service' +const logger = + vi.mocked(createLogger).mock.results[ + vi.mocked(createLogger).mock.calls.findIndex(([name]) => name === 'OutboxService') + ].value + function makePendingRow(overrides: Partial = {}): OutboxRow { return { id: 'evt-1', @@ -69,8 +75,7 @@ function holdLease() { /** Queue metadata discovery followed by individually claimed rows. */ function queuePendingEvents(rows: OutboxRow[]) { - queueTableRows( - outboxEvent, + dbChainMockFns.execute.mockResolvedValueOnce( [...new Set(rows.map(({ eventType }) => eventType))].map((eventType) => ({ eventType })) ) for (const row of rows) queueTableRows(outboxEvent, [row]) @@ -291,6 +296,68 @@ describe('processOutboxEvents — empty / no handler', () => { }) }) +describe('processOutboxEvents — infrastructure diagnostics', () => { + beforeEach(() => { + vi.clearAllMocks() + resetDbChainMock() + }) + + it('logs the nested database cause and rethrows discovery failures without exposing parameters', async () => { + const cause = Object.assign(new Error('canceling statement due to statement timeout'), { + code: '57014', + }) + const error = new Error('Failed query: select event_type\nparams: private-token', { cause }) + dbChainMockFns.execute.mockRejectedValueOnce(error) + + await expect(processOutboxEvents({})).rejects.toBe(error) + + expect(logger.error).toHaveBeenCalledWith( + 'Outbox processing failed', + expect.objectContaining({ + phase: 'discover', + processed: 0, + error: expect.objectContaining({ + code: '57014', + message: 'canceling statement due to statement timeout', + }), + }) + ) + expect(JSON.stringify(vi.mocked(logger.error).mock.calls)).not.toContain('private-token') + expect(dbChainMockFns.transaction).not.toHaveBeenCalled() + }) + + it('distinguishes a reaper failure from discovery and stops the poll', async () => { + const error = new Error('connection closed') + dbChainMockFns.returning.mockRejectedValueOnce(error) + + await expect(processOutboxEvents({})).rejects.toBe(error) + + expect(logger.error).toHaveBeenCalledWith( + 'Outbox processing failed', + expect.objectContaining({ phase: 'reap' }) + ) + expect(dbChainMockFns.execute).not.toHaveBeenCalled() + }) + + it('reports completed work when a later claim fails without rerunning handlers', async () => { + const handler = vi.fn(async () => {}) + const error = new Error('connection closed') + queuePendingEvents([makePendingRow()]) + holdLease() + dbChainMockFns.transaction + .mockImplementationOnce(async (callback) => callback(dbChainMock.db)) + .mockRejectedValueOnce(error) + + await expect(processOutboxEvents({ 'test.event': handler })).rejects.toBe(error) + + expect(logger.error).toHaveBeenCalledWith( + 'Outbox processing failed', + expect.objectContaining({ phase: 'claim', processed: 1 }) + ) + expect(handler).toHaveBeenCalledOnce() + }) +}) + describe('processOutboxEvents — handler success and retry', () => { beforeEach(() => { vi.clearAllMocks() @@ -542,7 +609,10 @@ describe('processOutboxEvents — handler timeout', () => { 550_000 ) const shortHandler = vi.fn(async () => {}) - queueTableRows(outboxEvent, [{ eventType: 'test.long' }, { eventType: 'test.short' }]) + dbChainMockFns.execute.mockResolvedValueOnce([ + { eventType: 'test.long' }, + { eventType: 'test.short' }, + ]) queueTableRows(outboxEvent, [makePendingRow({ eventType: 'test.short' })]) holdLease() diff --git a/apps/sim/lib/core/outbox/service.ts b/apps/sim/lib/core/outbox/service.ts index be1c6d4c254..967e2a6e487 100644 --- a/apps/sim/lib/core/outbox/service.ts +++ b/apps/sim/lib/core/outbox/service.ts @@ -1,17 +1,17 @@ import { db } from '@sim/db' import { outboxEvent } from '@sim/db/schema' import { createLogger } from '@sim/logger' -import { toError } from '@sim/utils/errors' +import { describeError, toError } from '@sim/utils/errors' import { generateId } from '@sim/utils/id' import { truncate } from '@sim/utils/string' import { and, asc, desc, eq, inArray, lte, sql } from 'drizzle-orm' +import { readyEventTypesQuery } from '@/lib/core/outbox/queries' const logger = createLogger('OutboxService') const DEFAULT_MAX_ATTEMPTS = 10 const MAX_BULK_ENQUEUE_EVENTS = 1_000 const MAX_PERSISTED_ERROR_LENGTH = 500 -const MAX_READY_EVENT_TYPES = 128 const MAX_REAPED_EVENTS = 1_000 /** @@ -426,67 +426,72 @@ export async function processOutboxEvents( handlers: OutboxHandlerRegistry, options: { batchSize?: number; maxRuntimeMs?: number; minRemainingMs?: number } = {} ): Promise { + const startedAt = Date.now() const batchSize = options.batchSize ?? 10 - const deadline = options.maxRuntimeMs ? Date.now() + options.maxRuntimeMs : undefined + const deadline = options.maxRuntimeMs ? startedAt + options.maxRuntimeMs : undefined const minRemainingMs = options.minRemainingMs ?? DEFAULT_HANDLER_TIMEOUT_MS + 5000 - - const reaped = await reapStuckProcessingRows() - + let phase = 'reap' + let reaped = 0 let processed = 0 let retried = 0 let deadLettered = 0 let leaseLost = 0 - const readyTypes = await findReadyEventTypes() - const eligibleTypes = readyTypes.map(({ eventType }) => eventType) - let cursor = 0 - let claimed = 0 - - while (claimed < batchSize && eligibleTypes.length > 0) { - if (deadline && Date.now() + minRemainingMs > deadline) break - if (cursor >= eligibleTypes.length) cursor = 0 - - const eventType = eligibleTypes[cursor] - const handlerTimeout = handlers[eventType]?.timeoutMs ?? DEFAULT_HANDLER_TIMEOUT_MS - if (deadline && Date.now() + handlerTimeout + 5000 > deadline) { - eligibleTypes.splice(cursor, 1) - continue - } - const [event] = await claimBatch(1, eventType) - if (!event) { - eligibleTypes.splice(cursor, 1) - continue - } - claimed++ - if (deadline && Date.now() + handlerTimeout + 5000 > deadline) { - await updateIfLeaseHeld(event, { status: 'pending', lockedAt: null }) - eligibleTypes.splice(cursor, 1) - continue - } - cursor++ - const result = await runHandler(event, handlers) - if (result === 'completed') processed++ - else if (result === 'dead_letter') deadLettered++ - else if (result === 'lease_lost') leaseLost++ - else retried++ - } + try { + reaped = await reapStuckProcessingRows() + phase = 'discover' + const readyTypes = await db.execute<{ eventType: string }>(readyEventTypesQuery(new Date())) + const eligibleTypes = readyTypes.map(({ eventType }) => eventType) + let cursor = 0 + let claimed = 0 + + while (claimed < batchSize && eligibleTypes.length > 0) { + if (deadline && Date.now() + minRemainingMs > deadline) break + if (cursor >= eligibleTypes.length) cursor = 0 + + const eventType = eligibleTypes[cursor] + const handlerTimeout = handlers[eventType]?.timeoutMs ?? DEFAULT_HANDLER_TIMEOUT_MS + if (deadline && Date.now() + handlerTimeout + 5000 > deadline) { + eligibleTypes.splice(cursor, 1) + continue + } - return { processed, retried, deadLettered, leaseLost, reaped } -} + phase = 'claim' + const [event] = await claimBatch(1, eventType) + if (!event) { + eligibleTypes.splice(cursor, 1) + continue + } + claimed++ + if (deadline && Date.now() + handlerTimeout + 5000 > deadline) { + phase = 'release' + await updateIfLeaseHeld(event, { status: 'pending', lockedAt: null }) + eligibleTypes.splice(cursor, 1) + continue + } + cursor++ + phase = 'handle' + const result = await runHandler(event, handlers) + if (result === 'completed') processed++ + else if (result === 'dead_letter') deadLettered++ + else if (result === 'lease_lost') leaseLost++ + else retried++ + } -/** - * Discover only queue metadata once per invocation. Indexed equality claims - * then avoid filtering and sorting the entire backlog for every delivered row. - * Event types are code-defined; the cap also bounds malformed or obsolete types. - */ -async function findReadyEventTypes(): Promise<{ eventType: string }[]> { - return db - .select({ eventType: outboxEvent.eventType }) - .from(outboxEvent) - .where(and(eq(outboxEvent.status, 'pending'), lte(outboxEvent.availableAt, new Date()))) - .groupBy(outboxEvent.eventType) - .orderBy(sql`min(${outboxEvent.availableAt})`, asc(outboxEvent.eventType)) - .limit(MAX_READY_EVENT_TYPES) + return { processed, retried, deadLettered, leaseLost, reaped } + } catch (error) { + logger.error('Outbox processing failed', { + phase, + durationMs: Date.now() - startedAt, + processed, + retried, + deadLettered, + leaseLost, + reaped, + error: describeError(error), + }) + throw error + } } /** diff --git a/packages/db/migrations/0357_outbox_autovacuum.sql b/packages/db/migrations/0357_outbox_autovacuum.sql new file mode 100644 index 00000000000..ba9b2f01409 --- /dev/null +++ b/packages/db/migrations/0357_outbox_autovacuum.sql @@ -0,0 +1,4 @@ +-- migration-safe: table-local maintenance settings only; no row rewrite or change to old readers and writers. +-- Status transitions leave obsolete entries in the pending index. Lower vacuum and +-- analyze thresholds keep index visibility and planner estimates aligned with queue churn. +ALTER TABLE "outbox_event" SET (autovacuum_vacuum_scale_factor = 0.02, autovacuum_analyze_scale_factor = 0.01); diff --git a/packages/db/migrations/meta/0357_snapshot.json b/packages/db/migrations/meta/0357_snapshot.json new file mode 100644 index 00000000000..884ca1c179c --- /dev/null +++ b/packages/db/migrations/meta/0357_snapshot.json @@ -0,0 +1,27509 @@ +{ + "id": "be1f2311-29ef-4e97-a7a8-cd008b64f8a5", + "prevId": "91de1d0e-ad87-4ad5-bef8-2c77aa34f2f7", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.academy_certificate": { + "name": "academy_certificate", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "course_id": { + "name": "course_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "academy_cert_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "issued_at": { + "name": "issued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "certificate_number": { + "name": "certificate_number", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "academy_certificate_user_id_idx": { + "name": "academy_certificate_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "academy_certificate_course_id_idx": { + "name": "academy_certificate_course_id_idx", + "columns": [ + { + "expression": "course_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "academy_certificate_user_course_unique": { + "name": "academy_certificate_user_course_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "course_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "academy_certificate_status_idx": { + "name": "academy_certificate_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "academy_certificate_user_id_user_id_fk": { + "name": "academy_certificate_user_id_user_id_fk", + "tableFrom": "academy_certificate", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "academy_certificate_certificate_number_unique": { + "name": "academy_certificate_certificate_number_unique", + "columns": ["certificate_number"], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oauth_config": { + "name": "oauth_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "account_user_id_idx": { + "name": "account_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_account_on_account_id_provider_id": { + "name": "idx_account_on_account_id_provider_id", + "columns": [ + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.api_key": { + "name": "api_key", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'personal'" + }, + "last_used": { + "name": "last_used", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "api_key_workspace_type_idx": { + "name": "api_key_workspace_type_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "api_key_user_type_idx": { + "name": "api_key_user_type_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "api_key_key_hash_idx": { + "name": "api_key_key_hash_idx", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "api_key_user_id_user_id_fk": { + "name": "api_key_user_id_user_id_fk", + "tableFrom": "api_key", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "api_key_workspace_id_workspace_id_fk": { + "name": "api_key_workspace_id_workspace_id_fk", + "tableFrom": "api_key", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "api_key_created_by_user_id_fk": { + "name": "api_key_created_by_user_id_fk", + "tableFrom": "api_key", + "columnsFrom": ["created_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "api_key_key_unique": { + "name": "api_key_key_unique", + "columns": ["key"], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "workspace_type_check": { + "name": "workspace_type_check", + "value": "(type = 'workspace' AND workspace_id IS NOT NULL) OR (type = 'personal' AND workspace_id IS NULL)" + } + }, + "isRLSEnabled": false + }, + "public.async_jobs": { + "name": "async_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "run_at": { + "name": "run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "max_attempts": { + "name": "max_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 3 + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "output": { + "name": "output", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "async_jobs_status_started_at_idx": { + "name": "async_jobs_status_started_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "async_jobs_status_completed_at_idx": { + "name": "async_jobs_status_completed_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "completed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "async_jobs_schedule_pending_run_at_idx": { + "name": "async_jobs_schedule_pending_run_at_idx", + "columns": [ + { + "expression": "run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"async_jobs\".\"type\" = 'schedule-execution' AND \"async_jobs\".\"status\" = 'pending'", + "concurrently": false + }, + "async_jobs_schedule_processing_started_at_idx": { + "name": "async_jobs_schedule_processing_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"async_jobs\".\"type\" = 'schedule-execution' AND \"async_jobs\".\"status\" = 'processing'", + "concurrently": false + }, + "async_jobs_schedule_unreconciled_terminal_idx": { + "name": "async_jobs_schedule_unreconciled_terminal_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"async_jobs\".\"type\" = 'schedule-execution' AND \"async_jobs\".\"status\" IN ('completed', 'failed', 'cancelled') AND COALESCE(\"async_jobs\".\"metadata\" ->> 'scheduleReconciled', 'false') <> 'true'", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.audit_log": { + "name": "audit_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_name": { + "name": "actor_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_email": { + "name": "actor_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resource_name": { + "name": "resource_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "surface": { + "name": "surface", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "audit_log_workspace_created_idx": { + "name": "audit_log_workspace_created_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "audit_log_workspace_created_at_id_idx": { + "name": "audit_log_workspace_created_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"created_at\")", + "isExpression": true, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "audit_log_actor_created_idx": { + "name": "audit_log_actor_created_idx", + "columns": [ + { + "expression": "actor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "audit_log_resource_idx": { + "name": "audit_log_resource_idx", + "columns": [ + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "audit_log_action_idx": { + "name": "audit_log_action_idx", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "audit_log_workspace_id_workspace_id_fk": { + "name": "audit_log_workspace_id_workspace_id_fk", + "tableFrom": "audit_log", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + }, + "audit_log_actor_id_user_id_fk": { + "name": "audit_log_actor_id_user_id_fk", + "tableFrom": "audit_log", + "columnsFrom": ["actor_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.background_work_status": { + "name": "background_work_status", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "background_work_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "background_work_status_value", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "background_work_status_workspace_status_idx": { + "name": "background_work_status_workspace_status_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "background_work_status_workflow_status_idx": { + "name": "background_work_status_workflow_status_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "background_work_status_meta_child_ws_idx": { + "name": "background_work_status_meta_child_ws_idx", + "columns": [ + { + "expression": "(\"metadata\" ->> 'childWorkspaceId')", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "background_work_status_meta_other_ws_idx": { + "name": "background_work_status_meta_other_ws_idx", + "columns": [ + { + "expression": "(\"metadata\" ->> 'otherWorkspaceId')", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "background_work_status_workspace_id_workspace_id_fk": { + "name": "background_work_status_workspace_id_workspace_id_fk", + "tableFrom": "background_work_status", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "background_work_status_workflow_id_workflow_id_fk": { + "name": "background_work_status_workflow_id_workflow_id_fk", + "tableFrom": "background_work_status", + "columnsFrom": ["workflow_id"], + "tableTo": "workflow", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat": { + "name": "chat", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "customizations": { + "name": "customizations", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'public'" + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "allowed_emails": { + "name": "allowed_emails", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "output_configs": { + "name": "output_configs", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "include_thinking": { + "name": "include_thinking", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "include_tool_calls": { + "name": "include_tool_calls", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "identifier_idx": { + "name": "identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"chat\".\"archived_at\" IS NULL", + "concurrently": false + }, + "chat_archived_at_partial_idx": { + "name": "chat_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"chat\".\"archived_at\" IS NOT NULL", + "concurrently": false + }, + "idx_chat_on_workflow_id_archived_at": { + "name": "idx_chat_on_workflow_id_archived_at", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "chat_workflow_id_workflow_id_fk": { + "name": "chat_workflow_id_workflow_id_fk", + "tableFrom": "chat", + "columnsFrom": ["workflow_id"], + "tableTo": "workflow", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "chat_user_id_user_id_fk": { + "name": "chat_user_id_user_id_fk", + "tableFrom": "chat", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_async_tool_calls": { + "name": "copilot_async_tool_calls", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "checkpoint_id": { + "name": "checkpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "tool_call_id": { + "name": "tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "args": { + "name": "args", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "status": { + "name": "status", + "type": "copilot_async_tool_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "permission_decision": { + "name": "permission_decision", + "type": "copilot_tool_permission_decision", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "permission_decided_at": { + "name": "permission_decided_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "claimed_by": { + "name": "claimed_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_async_tool_calls_run_id_idx": { + "name": "copilot_async_tool_calls_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_async_tool_calls_checkpoint_id_idx": { + "name": "copilot_async_tool_calls_checkpoint_id_idx", + "columns": [ + { + "expression": "checkpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_async_tool_calls_status_idx": { + "name": "copilot_async_tool_calls_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_async_tool_calls_run_status_idx": { + "name": "copilot_async_tool_calls_run_status_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_async_tool_calls_tool_call_id_unique": { + "name": "copilot_async_tool_calls_tool_call_id_unique", + "columns": [ + { + "expression": "tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "copilot_async_tool_calls_run_id_copilot_runs_id_fk": { + "name": "copilot_async_tool_calls_run_id_copilot_runs_id_fk", + "tableFrom": "copilot_async_tool_calls", + "columnsFrom": ["run_id"], + "tableTo": "copilot_runs", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "copilot_async_tool_calls_checkpoint_id_copilot_run_checkpoints_id_fk": { + "name": "copilot_async_tool_calls_checkpoint_id_copilot_run_checkpoints_id_fk", + "tableFrom": "copilot_async_tool_calls", + "columnsFrom": ["checkpoint_id"], + "tableTo": "copilot_run_checkpoints", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_chats": { + "name": "copilot_chats", + "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 + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "chat_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'copilot'" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'claude-3-7-sonnet-latest'" + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_conversation_key": { + "name": "external_conversation_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_conversation_metadata": { + "name": "external_conversation_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "preview_yaml": { + "name": "preview_yaml", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "plan_artifact": { + "name": "plan_artifact", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "resources": { + "name": "resources", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "auto_allowed_tools": { + "name": "auto_allowed_tools", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "pinned": { + "name": "pinned", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_chats_organization_id_idx": { + "name": "copilot_chats_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_chats_external_conversation_unique": { + "name": "copilot_chats_external_conversation_unique", + "columns": [ + { + "expression": "external_conversation_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"copilot_chats\".\"external_conversation_key\" IS NOT NULL", + "concurrently": false + }, + "copilot_chats_user_org_created_idx": { + "name": "copilot_chats_user_org_created_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_chats_user_id_idx": { + "name": "copilot_chats_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_chats_workflow_id_idx": { + "name": "copilot_chats_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_chats_user_workflow_idx": { + "name": "copilot_chats_user_workflow_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_chats_user_workspace_idx": { + "name": "copilot_chats_user_workspace_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_chats_created_at_idx": { + "name": "copilot_chats_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_chats_updated_at_idx": { + "name": "copilot_chats_updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_chats_workspace_created_at_id_idx": { + "name": "copilot_chats_workspace_created_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"created_at\")", + "isExpression": true, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_chats_user_workspace_deleted_partial_idx": { + "name": "copilot_chats_user_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"copilot_chats\".\"deleted_at\" IS NOT NULL", + "concurrently": false + } + }, + "foreignKeys": { + "copilot_chats_user_id_user_id_fk": { + "name": "copilot_chats_user_id_user_id_fk", + "tableFrom": "copilot_chats", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "copilot_chats_workflow_id_workflow_id_fk": { + "name": "copilot_chats_workflow_id_workflow_id_fk", + "tableFrom": "copilot_chats", + "columnsFrom": ["workflow_id"], + "tableTo": "workflow", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "copilot_chats_workspace_id_workspace_id_fk": { + "name": "copilot_chats_workspace_id_workspace_id_fk", + "tableFrom": "copilot_chats", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "copilot_chats_organization_id_organization_id_fk": { + "name": "copilot_chats_organization_id_organization_id_fk", + "tableFrom": "copilot_chats", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "copilot_chats_owner_check": { + "name": "copilot_chats_owner_check", + "value": "num_nonnulls(\"copilot_chats\".\"workspace_id\", \"copilot_chats\".\"organization_id\") <= 1" + }, + "copilot_chats_organization_workflow_check": { + "name": "copilot_chats_organization_workflow_check", + "value": "\"copilot_chats\".\"organization_id\" IS NULL OR \"copilot_chats\".\"workflow_id\" IS NULL" + } + }, + "isRLSEnabled": false + }, + "public.copilot_feedback": { + "name": "copilot_feedback", + "schema": "", + "columns": { + "feedback_id": { + "name": "feedback_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_query": { + "name": "user_query", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_response": { + "name": "agent_response", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_positive": { + "name": "is_positive", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "feedback": { + "name": "feedback", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_yaml": { + "name": "workflow_yaml", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_feedback_user_id_idx": { + "name": "copilot_feedback_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_feedback_chat_id_idx": { + "name": "copilot_feedback_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_feedback_user_chat_idx": { + "name": "copilot_feedback_user_chat_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_feedback_is_positive_idx": { + "name": "copilot_feedback_is_positive_idx", + "columns": [ + { + "expression": "is_positive", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_feedback_created_at_idx": { + "name": "copilot_feedback_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "copilot_feedback_user_id_user_id_fk": { + "name": "copilot_feedback_user_id_user_id_fk", + "tableFrom": "copilot_feedback", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "copilot_feedback_chat_id_copilot_chats_id_fk": { + "name": "copilot_feedback_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_feedback", + "columnsFrom": ["chat_id"], + "tableTo": "copilot_chats", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_messages": { + "name": "copilot_messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "stream_id": { + "name": "stream_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "parent_message_id": { + "name": "parent_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tokens_in": { + "name": "tokens_in", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "tokens_out": { + "name": "tokens_out", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "seq": { + "name": "seq", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_messages_chat_message_unique": { + "name": "copilot_messages_chat_message_unique", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_messages_chat_created_at_idx": { + "name": "copilot_messages_chat_created_at_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"copilot_messages\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "copilot_messages_chat_seq_idx": { + "name": "copilot_messages_chat_seq_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "seq", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"copilot_messages\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "copilot_messages_chat_stream_idx": { + "name": "copilot_messages_chat_stream_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stream_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"copilot_messages\".\"stream_id\" IS NOT NULL", + "concurrently": false + }, + "copilot_messages_user_created_at_idx": { + "name": "copilot_messages_user_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"copilot_messages\".\"role\" = 'user' AND \"copilot_messages\".\"deleted_at\" IS NULL", + "concurrently": false + } + }, + "foreignKeys": { + "copilot_messages_chat_id_copilot_chats_id_fk": { + "name": "copilot_messages_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_messages", + "columnsFrom": ["chat_id"], + "tableTo": "copilot_chats", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_run_checkpoints": { + "name": "copilot_run_checkpoints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pending_tool_call_id": { + "name": "pending_tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "conversation_snapshot": { + "name": "conversation_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "agent_state": { + "name": "agent_state", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "provider_request": { + "name": "provider_request", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_run_checkpoints_run_id_idx": { + "name": "copilot_run_checkpoints_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_run_checkpoints_pending_tool_call_id_idx": { + "name": "copilot_run_checkpoints_pending_tool_call_id_idx", + "columns": [ + { + "expression": "pending_tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_run_checkpoints_run_pending_tool_unique": { + "name": "copilot_run_checkpoints_run_pending_tool_unique", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pending_tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "copilot_run_checkpoints_run_id_copilot_runs_id_fk": { + "name": "copilot_run_checkpoints_run_id_copilot_runs_id_fk", + "tableFrom": "copilot_run_checkpoints", + "columnsFrom": ["run_id"], + "tableTo": "copilot_runs", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_runs": { + "name": "copilot_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_run_id": { + "name": "parent_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stream_id": { + "name": "stream_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent": { + "name": "agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "copilot_run_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "request_context": { + "name": "request_context", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "copilot_runs_execution_id_idx": { + "name": "copilot_runs_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_runs_parent_run_id_idx": { + "name": "copilot_runs_parent_run_id_idx", + "columns": [ + { + "expression": "parent_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_runs_chat_id_idx": { + "name": "copilot_runs_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_runs_chat_started_at_idx": { + "name": "copilot_runs_chat_started_at_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_runs_user_id_idx": { + "name": "copilot_runs_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_runs_workflow_id_idx": { + "name": "copilot_runs_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_runs_workspace_id_idx": { + "name": "copilot_runs_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_runs_status_idx": { + "name": "copilot_runs_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_runs_chat_execution_idx": { + "name": "copilot_runs_chat_execution_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_runs_execution_started_at_idx": { + "name": "copilot_runs_execution_started_at_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_runs_workspace_completed_at_id_idx": { + "name": "copilot_runs_workspace_completed_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"completed_at\")", + "isExpression": true, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_runs_stream_id_unique": { + "name": "copilot_runs_stream_id_unique", + "columns": [ + { + "expression": "stream_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "copilot_runs_chat_id_copilot_chats_id_fk": { + "name": "copilot_runs_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_runs", + "columnsFrom": ["chat_id"], + "tableTo": "copilot_chats", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "copilot_runs_user_id_user_id_fk": { + "name": "copilot_runs_user_id_user_id_fk", + "tableFrom": "copilot_runs", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "copilot_runs_workflow_id_workflow_id_fk": { + "name": "copilot_runs_workflow_id_workflow_id_fk", + "tableFrom": "copilot_runs", + "columnsFrom": ["workflow_id"], + "tableTo": "workflow", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "copilot_runs_workspace_id_workspace_id_fk": { + "name": "copilot_runs_workspace_id_workspace_id_fk", + "tableFrom": "copilot_runs", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_workflow_read_hashes": { + "name": "copilot_workflow_read_hashes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_workflow_read_hashes_chat_id_idx": { + "name": "copilot_workflow_read_hashes_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_workflow_read_hashes_workflow_id_idx": { + "name": "copilot_workflow_read_hashes_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_workflow_read_hashes_chat_workflow_unique": { + "name": "copilot_workflow_read_hashes_chat_workflow_unique", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "copilot_workflow_read_hashes_chat_id_copilot_chats_id_fk": { + "name": "copilot_workflow_read_hashes_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_workflow_read_hashes", + "columnsFrom": ["chat_id"], + "tableTo": "copilot_chats", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "copilot_workflow_read_hashes_workflow_id_workflow_id_fk": { + "name": "copilot_workflow_read_hashes_workflow_id_workflow_id_fk", + "tableFrom": "copilot_workflow_read_hashes", + "columnsFrom": ["workflow_id"], + "tableTo": "workflow", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.credential": { + "name": "credential", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slack_app_id": { + "name": "slack_app_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "credential_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "unredacted": { + "name": "unredacted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env_key": { + "name": "env_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env_owner_user_id": { + "name": "env_owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_service_account_key": { + "name": "encrypted_service_account_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_personal_token": { + "name": "encrypted_personal_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "authorization_app_id": { + "name": "authorization_app_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_group_enrollment_id": { + "name": "credential_group_enrollment_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_group_option_id": { + "name": "credential_group_option_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mcp_server_id": { + "name": "mcp_server_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mcp_oauth_config_version": { + "name": "mcp_oauth_config_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "managed_oauth_scope_version": { + "name": "managed_oauth_scope_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "provider_subject_id": { + "name": "provider_subject_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_tenant_id": { + "name": "provider_tenant_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "managed_oauth_status": { + "name": "managed_oauth_status", + "type": "managed_oauth_credential_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "granted_scopes": { + "name": "granted_scopes", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "provider_metadata": { + "name": "provider_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "encrypted_oauth_token_set": { + "name": "encrypted_oauth_token_set", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mcp_tools": { + "name": "mcp_tools", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "mcp_tools_refreshed_at": { + "name": "mcp_tools_refreshed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "granted_at": { + "name": "granted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_refreshed_at": { + "name": "last_refreshed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "credential_organization_id_idx": { + "name": "credential_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "credential_organization_account_unique": { + "name": "credential_organization_account_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"credential\".\"account_id\" IS NOT NULL", + "concurrently": false + }, + "credential_org_personal_token_unique": { + "name": "credential_org_personal_token_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_subject_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"credential\".\"type\" = 'personal_token'", + "concurrently": false + }, + "credential_workspace_id_idx": { + "name": "credential_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "credential_type_idx": { + "name": "credential_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "credential_provider_id_idx": { + "name": "credential_provider_id_idx", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "credential_account_id_idx": { + "name": "credential_account_id_idx", + "columns": [ + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "credential_env_owner_user_id_idx": { + "name": "credential_env_owner_user_id_idx", + "columns": [ + { + "expression": "env_owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "credential_group_enrollment_idx": { + "name": "credential_group_enrollment_idx", + "columns": [ + { + "expression": "credential_group_enrollment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "credential_mcp_server_idx": { + "name": "credential_mcp_server_idx", + "columns": [ + { + "expression": "mcp_server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "credential_group_option_unique": { + "name": "credential_group_option_unique", + "columns": [ + { + "expression": "credential_group_enrollment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "credential_group_option_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"credential\".\"type\" = 'managed_oauth'", + "concurrently": false + }, + "credential_managed_mcp_enrollment_server_unique": { + "name": "credential_managed_mcp_enrollment_server_unique", + "columns": [ + { + "expression": "credential_group_enrollment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "mcp_server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"credential\".\"type\" = 'managed_mcp'", + "concurrently": false + }, + "credential_workspace_account_unique": { + "name": "credential_workspace_account_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "account_id IS NOT NULL", + "concurrently": false + }, + "credential_workspace_env_unique": { + "name": "credential_workspace_env_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "type = 'env_workspace'", + "concurrently": false + }, + "credential_workspace_personal_env_unique": { + "name": "credential_workspace_personal_env_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env_owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "type = 'env_personal'", + "concurrently": false + }, + "credential_personal_token_identity_unique": { + "name": "credential_personal_token_identity_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_subject_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "type = 'personal_token'", + "concurrently": false + } + }, + "foreignKeys": { + "credential_workspace_id_workspace_id_fk": { + "name": "credential_workspace_id_workspace_id_fk", + "tableFrom": "credential", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "credential_organization_id_organization_id_fk": { + "name": "credential_organization_id_organization_id_fk", + "tableFrom": "credential", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "credential_slack_app_id_slack_app_id_fk": { + "name": "credential_slack_app_id_slack_app_id_fk", + "tableFrom": "credential", + "columnsFrom": ["slack_app_id"], + "tableTo": "slack_app", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "no action" + }, + "credential_account_id_account_id_fk": { + "name": "credential_account_id_account_id_fk", + "tableFrom": "credential", + "columnsFrom": ["account_id"], + "tableTo": "account", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "credential_env_owner_user_id_user_id_fk": { + "name": "credential_env_owner_user_id_user_id_fk", + "tableFrom": "credential", + "columnsFrom": ["env_owner_user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "credential_credential_group_enrollment_id_credential_group_enrollment_id_fk": { + "name": "credential_credential_group_enrollment_id_credential_group_enrollment_id_fk", + "tableFrom": "credential", + "columnsFrom": ["credential_group_enrollment_id"], + "tableTo": "credential_group_enrollment", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "credential_mcp_server_id_mcp_servers_id_fk": { + "name": "credential_mcp_server_id_mcp_servers_id_fk", + "tableFrom": "credential", + "columnsFrom": ["mcp_server_id"], + "tableTo": "mcp_servers", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "credential_created_by_user_id_fk": { + "name": "credential_created_by_user_id_fk", + "tableFrom": "credential", + "columnsFrom": ["created_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "credential_owner_check": { + "name": "credential_owner_check", + "value": "num_nonnulls(\"credential\".\"workspace_id\", \"credential\".\"organization_id\") = 1" + }, + "credential_organization_type_check": { + "name": "credential_organization_type_check", + "value": "\"credential\".\"organization_id\" IS NULL OR \"credential\".\"type\" IN ('oauth', 'managed_oauth', 'managed_mcp', 'service_account', 'personal_token')" + }, + "credential_personal_token_source_check": { + "name": "credential_personal_token_source_check", + "value": "(type::text <> 'personal_token') OR (\n created_by IS NOT NULL\n AND provider_id IS NOT NULL\n AND provider_id = 'gitlab'\n AND provider_subject_id IS NOT NULL\n AND provider_tenant_id IS NOT NULL\n AND encrypted_personal_token IS NOT NULL\n AND granted_scopes IS NOT NULL\n AND cardinality(granted_scopes) > 0\n AND account_id IS NULL\n AND env_key IS NULL\n AND env_owner_user_id IS NULL\n AND authorization_app_id IS NULL\n AND encrypted_oauth_token_set IS NULL\n AND encrypted_service_account_key IS NULL\n AND unredacted = false\n )" + }, + "credential_oauth_source_check": { + "name": "credential_oauth_source_check", + "value": "(type <> 'oauth') OR (account_id IS NOT NULL AND provider_id IS NOT NULL)" + }, + "credential_managed_oauth_source_check": { + "name": "credential_managed_oauth_source_check", + "value": "(type::text <> 'managed_oauth') OR (\n account_id IS NULL\n AND provider_id IS NOT NULL\n AND authorization_app_id IS NOT NULL\n AND provider_subject_id IS NOT NULL\n AND managed_oauth_status IS NOT NULL\n AND granted_scopes IS NOT NULL\n AND encrypted_oauth_token_set IS NOT NULL\n AND granted_at IS NOT NULL\n )" + }, + "credential_managed_oauth_group_binding_check": { + "name": "credential_managed_oauth_group_binding_check", + "value": "(type::text <> 'managed_oauth') OR (\n credential_group_enrollment_id IS NOT NULL\n AND credential_group_option_id IS NOT NULL\n AND managed_oauth_scope_version IS NOT NULL\n AND managed_oauth_scope_version > 0\n )" + }, + "credential_managed_mcp_source_check": { + "name": "credential_managed_mcp_source_check", + "value": "(type::text <> 'managed_mcp') OR (\n id LIKE 'mcp-cg-%'\n AND account_id IS NULL\n AND provider_id IS NULL\n AND authorization_app_id IS NULL\n AND credential_group_enrollment_id IS NOT NULL\n AND credential_group_option_id IS NULL\n AND mcp_server_id IS NOT NULL\n AND managed_oauth_status IS NOT NULL\n AND (managed_oauth_status <> 'active' OR (\n encrypted_oauth_token_set IS NOT NULL\n AND mcp_tools IS NOT NULL\n ))\n AND granted_at IS NOT NULL\n AND managed_oauth_scope_version IS NULL\n AND provider_subject_id IS NULL\n AND provider_tenant_id IS NULL\n AND granted_scopes IS NULL\n AND provider_metadata IS NULL\n AND created_by IS NULL\n AND env_key IS NULL\n AND env_owner_user_id IS NULL\n AND encrypted_service_account_key IS NULL\n AND unredacted = false\n )" + }, + "credential_creator_source_check": { + "name": "credential_creator_source_check", + "value": "(type::text = 'managed_mcp') OR created_by IS NOT NULL" + }, + "credential_workspace_env_source_check": { + "name": "credential_workspace_env_source_check", + "value": "(type <> 'env_workspace') OR (env_key IS NOT NULL AND env_owner_user_id IS NULL)" + }, + "credential_personal_env_source_check": { + "name": "credential_personal_env_source_check", + "value": "(type <> 'env_personal') OR (env_key IS NOT NULL AND env_owner_user_id IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.credential_group": { + "name": "credential_group", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "public_id": { + "name": "public_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "options": { + "name": "options", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "encrypted_provider_configuration": { + "name": "encrypted_provider_configuration", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "credential_group_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "credential_group_organization_id_idx": { + "name": "credential_group_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "credential_group_organization_unique": { + "name": "credential_group_organization_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "credential_group_public_id_unique": { + "name": "credential_group_public_id_unique", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "credential_group_workspace_unique": { + "name": "credential_group_workspace_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "credential_group_workspace_id_workspace_id_fk": { + "name": "credential_group_workspace_id_workspace_id_fk", + "tableFrom": "credential_group", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "credential_group_organization_id_organization_id_fk": { + "name": "credential_group_organization_id_organization_id_fk", + "tableFrom": "credential_group", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "credential_group_created_by_user_id_fk": { + "name": "credential_group_created_by_user_id_fk", + "tableFrom": "credential_group", + "columnsFrom": ["created_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "credential_group_owner_check": { + "name": "credential_group_owner_check", + "value": "num_nonnulls(\"credential_group\".\"workspace_id\", \"credential_group\".\"organization_id\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.credential_group_enrollment": { + "name": "credential_group_enrollment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "credential_group_id": { + "name": "credential_group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "credential_group_enrollment_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'invited'" + }, + "invitation_token_hash": { + "name": "invitation_token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "invitation_expires_at": { + "name": "invitation_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "invited_at": { + "name": "invited_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "sent_at": { + "name": "sent_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_delivery_error": { + "name": "last_delivery_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "credential_group_enrollment_group_user_unique": { + "name": "credential_group_enrollment_group_user_unique", + "columns": [ + { + "expression": "credential_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"credential_group_enrollment\".\"user_id\" IS NOT NULL", + "concurrently": false + }, + "credential_group_enrollment_user_id_idx": { + "name": "credential_group_enrollment_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "credential_group_enrollment_group_email_unique": { + "name": "credential_group_enrollment_group_email_unique", + "columns": [ + { + "expression": "credential_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "credential_group_enrollment_invitation_token_hash_unique": { + "name": "credential_group_enrollment_invitation_token_hash_unique", + "columns": [ + { + "expression": "invitation_token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "credential_group_enrollment_group_status_idx": { + "name": "credential_group_enrollment_group_status_idx", + "columns": [ + { + "expression": "credential_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "credential_group_enrollment_group_invited_at_id_idx": { + "name": "credential_group_enrollment_group_invited_at_id_idx", + "columns": [ + { + "expression": "credential_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "invited_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "credential_group_enrollment_credential_group_id_credential_group_id_fk": { + "name": "credential_group_enrollment_credential_group_id_credential_group_id_fk", + "tableFrom": "credential_group_enrollment", + "columnsFrom": ["credential_group_id"], + "tableTo": "credential_group", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "credential_group_enrollment_user_id_user_id_fk": { + "name": "credential_group_enrollment_user_id_user_id_fk", + "tableFrom": "credential_group_enrollment", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "credential_group_enrollment_created_by_user_id_fk": { + "name": "credential_group_enrollment_created_by_user_id_fk", + "tableFrom": "credential_group_enrollment", + "columnsFrom": ["created_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "credential_group_enrollment_normalized_email_check": { + "name": "credential_group_enrollment_normalized_email_check", + "value": "\"credential_group_enrollment\".\"email\" = lower(btrim(\"credential_group_enrollment\".\"email\")) AND length(\"credential_group_enrollment\".\"email\") BETWEEN 3 AND 320" + }, + "credential_group_enrollment_invitation_token_hash_length_check": { + "name": "credential_group_enrollment_invitation_token_hash_length_check", + "value": "length(\"credential_group_enrollment\".\"invitation_token_hash\") = 64" + } + }, + "isRLSEnabled": false + }, + "public.credential_member": { + "name": "credential_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "credential_member_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "status": { + "name": "status", + "type": "credential_member_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "invited_by": { + "name": "invited_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "credential_member_user_id_idx": { + "name": "credential_member_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "credential_member_role_idx": { + "name": "credential_member_role_idx", + "columns": [ + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "credential_member_status_idx": { + "name": "credential_member_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "credential_member_unique": { + "name": "credential_member_unique", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "credential_member_credential_id_credential_id_fk": { + "name": "credential_member_credential_id_credential_id_fk", + "tableFrom": "credential_member", + "columnsFrom": ["credential_id"], + "tableTo": "credential", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "credential_member_user_id_user_id_fk": { + "name": "credential_member_user_id_user_id_fk", + "tableFrom": "credential_member", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "credential_member_invited_by_user_id_fk": { + "name": "credential_member_invited_by_user_id_fk", + "tableFrom": "credential_member", + "columnsFrom": ["invited_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.custom_block": { + "name": "custom_block", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "icon_url": { + "name": "icon_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "inputs": { + "name": "inputs", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "outputs": { + "name": "outputs", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "trace_child_runs": { + "name": "trace_child_runs", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "custom_block_organization_id_idx": { + "name": "custom_block_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "custom_block_workflow_id_idx": { + "name": "custom_block_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "custom_block_organization_type_unique": { + "name": "custom_block_organization_type_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "custom_block_organization_id_organization_id_fk": { + "name": "custom_block_organization_id_organization_id_fk", + "tableFrom": "custom_block", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "custom_block_workflow_id_workflow_id_fk": { + "name": "custom_block_workflow_id_workflow_id_fk", + "tableFrom": "custom_block", + "columnsFrom": ["workflow_id"], + "tableTo": "workflow", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "custom_block_created_by_user_id_fk": { + "name": "custom_block_created_by_user_id_fk", + "tableFrom": "custom_block", + "columnsFrom": ["created_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.custom_tools": { + "name": "custom_tools", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schema": { + "name": "schema", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "custom_tools_workspace_id_idx": { + "name": "custom_tools_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "custom_tools_workspace_title_unique": { + "name": "custom_tools_workspace_title_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "custom_tools_workspace_id_workspace_id_fk": { + "name": "custom_tools_workspace_id_workspace_id_fk", + "tableFrom": "custom_tools", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "custom_tools_user_id_user_id_fk": { + "name": "custom_tools_user_id_user_id_fk", + "tableFrom": "custom_tools", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.data_drain_runs": { + "name": "data_drain_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "drain_id": { + "name": "drain_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "data_drain_run_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "trigger": { + "name": "trigger", + "type": "data_drain_run_trigger", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "rows_exported": { + "name": "rows_exported", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "bytes_written": { + "name": "bytes_written", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cursor_before": { + "name": "cursor_before", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cursor_after": { + "name": "cursor_after", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "locators": { + "name": "locators", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": { + "data_drain_runs_drain_started_idx": { + "name": "data_drain_runs_drain_started_idx", + "columns": [ + { + "expression": "drain_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "data_drain_runs_drain_id_data_drains_id_fk": { + "name": "data_drain_runs_drain_id_data_drains_id_fk", + "tableFrom": "data_drain_runs", + "columnsFrom": ["drain_id"], + "tableTo": "data_drains", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.data_drains": { + "name": "data_drains", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "data_drain_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "destination_type": { + "name": "destination_type", + "type": "data_drain_destination", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "destination_config": { + "name": "destination_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "destination_credentials": { + "name": "destination_credentials", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schedule_cadence": { + "name": "schedule_cadence", + "type": "data_drain_cadence", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "cursor": { + "name": "cursor", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_success_at": { + "name": "last_success_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "data_drains_org_idx": { + "name": "data_drains_org_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "data_drains_due_idx": { + "name": "data_drains_due_idx", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "data_drains_org_name_unique": { + "name": "data_drains_org_name_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "data_drains_organization_id_organization_id_fk": { + "name": "data_drains_organization_id_organization_id_fk", + "tableFrom": "data_drains", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "data_drains_created_by_user_id_fk": { + "name": "data_drains_created_by_user_id_fk", + "tableFrom": "data_drains", + "columnsFrom": ["created_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.docs_embeddings": { + "name": "docs_embeddings", + "schema": "", + "columns": { + "chunk_id": { + "name": "chunk_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "chunk_text": { + "name": "chunk_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_document": { + "name": "source_document", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_link": { + "name": "source_link", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "header_text": { + "name": "header_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "header_level": { + "name": "header_level", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "embedding": { + "name": "embedding", + "type": "vector(1536)", + "primaryKey": false, + "notNull": true + }, + "embedding_model": { + "name": "embedding_model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text-embedding-3-small'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "chunk_text_tsv": { + "name": "chunk_text_tsv", + "type": "tsvector", + "primaryKey": false, + "notNull": false, + "generated": { + "type": "stored", + "as": "to_tsvector('english', \"docs_embeddings\".\"chunk_text\")" + } + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "docs_emb_source_document_idx": { + "name": "docs_emb_source_document_idx", + "columns": [ + { + "expression": "source_document", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "docs_emb_header_level_idx": { + "name": "docs_emb_header_level_idx", + "columns": [ + { + "expression": "header_level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "docs_emb_source_header_idx": { + "name": "docs_emb_source_header_idx", + "columns": [ + { + "expression": "source_document", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "header_level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "docs_emb_model_idx": { + "name": "docs_emb_model_idx", + "columns": [ + { + "expression": "embedding_model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "docs_emb_created_at_idx": { + "name": "docs_emb_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "docs_embedding_vector_hnsw_idx": { + "name": "docs_embedding_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "with": { + "m": 16, + "ef_construction": 64 + }, + "method": "hnsw", + "concurrently": false + }, + "docs_emb_metadata_gin_idx": { + "name": "docs_emb_metadata_gin_idx", + "columns": [ + { + "expression": "metadata", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "gin", + "concurrently": false + }, + "docs_emb_chunk_text_fts_idx": { + "name": "docs_emb_chunk_text_fts_idx", + "columns": [ + { + "expression": "chunk_text_tsv", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "gin", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "docs_embedding_not_null_check": { + "name": "docs_embedding_not_null_check", + "value": "\"embedding\" IS NOT NULL" + }, + "docs_header_level_check": { + "name": "docs_header_level_check", + "value": "\"header_level\" >= 1 AND \"header_level\" <= 6" + } + }, + "isRLSEnabled": false + }, + "public.document": { + "name": "document", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "filename": { + "name": "filename", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_url": { + "name": "file_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "storage_key": { + "name": "storage_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "file_size": { + "name": "file_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chunk_count": { + "name": "chunk_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "character_count": { + "name": "character_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "processing_status": { + "name": "processing_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "processing_attempts": { + "name": "processing_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "processing_queued_at": { + "name": "processing_queued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "processing_queue_token": { + "name": "processing_queue_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "processing_started_at": { + "name": "processing_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "processing_deferred_until": { + "name": "processing_deferred_until", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "processing_completed_at": { + "name": "processing_completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "processing_error": { + "name": "processing_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "processing_recovery_after": { + "name": "processing_recovery_after", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "user_excluded": { + "name": "user_excluded", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "tag1": { + "name": "tag1", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag2": { + "name": "tag2", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag3": { + "name": "tag3", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag4": { + "name": "tag4", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag5": { + "name": "tag5", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag6": { + "name": "tag6", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag7": { + "name": "tag7", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "number1": { + "name": "number1", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number2": { + "name": "number2", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number3": { + "name": "number3", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number4": { + "name": "number4", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number5": { + "name": "number5", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "date1": { + "name": "date1", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "date2": { + "name": "date2", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "boolean1": { + "name": "boolean1", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean2": { + "name": "boolean2", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean3": { + "name": "boolean3", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_url": { + "name": "source_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_provenance_version": { + "name": "secret_provenance_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "uploaded_by": { + "name": "uploaded_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "acl": { + "name": "acl", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{ws}'::text[]" + }, + "acl_requirements": { + "name": "acl_requirements", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "acl_verified_at": { + "name": "acl_verified_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "source_modified_at": { + "name": "source_modified_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "source_seen_at": { + "name": "source_seen_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "doc_kb_id_idx": { + "name": "doc_kb_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "doc_acl_gin_idx": { + "name": "doc_acl_gin_idx", + "columns": [ + { + "expression": "acl", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "array_ops" + } + ], + "isUnique": false, + "with": {}, + "method": "gin", + "where": "\"document\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "doc_filename_idx": { + "name": "doc_filename_idx", + "columns": [ + { + "expression": "filename", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "doc_processing_status_idx": { + "name": "doc_processing_status_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "processing_status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "doc_processing_recovery_idx": { + "name": "doc_processing_recovery_idx", + "columns": [ + { + "expression": "uploaded_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"document\".\"processing_status\" IN ('pending', 'processing', 'failed') AND \"document\".\"connector_id\" IS NOT NULL AND \"document\".\"content_hash\" IS NOT NULL AND \"document\".\"storage_key\" IS NOT NULL AND \"document\".\"user_excluded\" = false AND \"document\".\"archived_at\" IS NULL AND \"document\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "doc_connector_processing_status_idx": { + "name": "doc_connector_processing_status_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "processing_status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"document\".\"processing_status\" IN ('pending', 'processing', 'failed') AND \"document\".\"connector_id\" IS NOT NULL AND \"document\".\"user_excluded\" = false AND \"document\".\"archived_at\" IS NULL AND \"document\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "doc_connector_external_id_idx": { + "name": "doc_connector_external_id_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"document\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "doc_connector_source_lookup_idx": { + "name": "doc_connector_source_lookup_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "doc_connector_reconciliation_idx": { + "name": "doc_connector_reconciliation_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "COALESCE(\"source_seen_at\", '-infinity'::timestamp)", + "isExpression": true, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"document\".\"user_excluded\" = false AND \"document\".\"archived_at\" IS NULL", + "concurrently": false + }, + "doc_active_kb_token_count_idx": { + "name": "doc_active_kb_token_count_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "token_count", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"document\".\"user_excluded\" = false AND \"document\".\"archived_at\" IS NULL AND \"document\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "doc_storage_key_idx": { + "name": "doc_storage_key_idx", + "columns": [ + { + "expression": "storage_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"document\".\"storage_key\" IS NOT NULL", + "concurrently": false + }, + "doc_archived_at_partial_idx": { + "name": "doc_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"document\".\"archived_at\" IS NOT NULL", + "concurrently": false + }, + "doc_deleted_at_partial_idx": { + "name": "doc_deleted_at_partial_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"document\".\"deleted_at\" IS NOT NULL", + "concurrently": false + }, + "doc_kb_tag1_lower_idx": { + "name": "doc_kb_tag1_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag1\")", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "doc_kb_tag2_lower_idx": { + "name": "doc_kb_tag2_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag2\")", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "doc_kb_tag3_lower_idx": { + "name": "doc_kb_tag3_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag3\")", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "doc_kb_tag4_lower_idx": { + "name": "doc_kb_tag4_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag4\")", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "doc_kb_tag5_lower_idx": { + "name": "doc_kb_tag5_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag5\")", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "doc_kb_tag6_lower_idx": { + "name": "doc_kb_tag6_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag6\")", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "doc_kb_tag7_lower_idx": { + "name": "doc_kb_tag7_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag7\")", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "doc_number1_idx": { + "name": "doc_number1_idx", + "columns": [ + { + "expression": "number1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "doc_number2_idx": { + "name": "doc_number2_idx", + "columns": [ + { + "expression": "number2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "doc_number3_idx": { + "name": "doc_number3_idx", + "columns": [ + { + "expression": "number3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "doc_number4_idx": { + "name": "doc_number4_idx", + "columns": [ + { + "expression": "number4", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "doc_number5_idx": { + "name": "doc_number5_idx", + "columns": [ + { + "expression": "number5", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "doc_date1_idx": { + "name": "doc_date1_idx", + "columns": [ + { + "expression": "date1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "doc_date2_idx": { + "name": "doc_date2_idx", + "columns": [ + { + "expression": "date2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "doc_boolean1_idx": { + "name": "doc_boolean1_idx", + "columns": [ + { + "expression": "boolean1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "doc_boolean2_idx": { + "name": "doc_boolean2_idx", + "columns": [ + { + "expression": "boolean2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "doc_boolean3_idx": { + "name": "doc_boolean3_idx", + "columns": [ + { + "expression": "boolean3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "document_knowledge_base_id_knowledge_base_id_fk": { + "name": "document_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "document", + "columnsFrom": ["knowledge_base_id"], + "tableTo": "knowledge_base", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "document_connector_id_knowledge_connector_id_fk": { + "name": "document_connector_id_knowledge_connector_id_fk", + "tableFrom": "document", + "columnsFrom": ["connector_id"], + "tableTo": "knowledge_connector", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + }, + "document_uploaded_by_user_id_fk": { + "name": "document_uploaded_by_user_id_fk", + "tableFrom": "document", + "columnsFrom": ["uploaded_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "doc_acl_token_shape_check": { + "name": "doc_acl_token_shape_check", + "value": "array_position(\"document\".\"acl\", NULL) IS NULL AND (cardinality(\"document\".\"acl\") = 0 OR (cardinality(\"document\".\"acl\") = array_length(string_to_array(array_to_string(\"document\".\"acl\", E'\\n'), E'\\n'), 1) AND array_to_string(\"document\".\"acl\", E'\\n') ~ '^((ws|pub|link|u:[^\\nA-Z]+@[^\\nA-Z]+|[gs]:[^\\n:]+:[^\\n:]+:[^\\n]+)(\\n(ws|pub|link|u:[^\\nA-Z]+@[^\\nA-Z]+|[gs]:[^\\n:]+:[^\\n:]+:[^\\n]+))*)$'))" + } + }, + "isRLSEnabled": false + }, + "public.document_secret_provenance": { + "name": "document_secret_provenance", + "schema": "", + "columns": { + "document_id": { + "name": "document_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "source_hash": { + "name": "source_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entries": { + "name": "entries", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "document_secret_provenance_document_id_document_id_fk": { + "name": "document_secret_provenance_document_id_document_id_fk", + "tableFrom": "document_secret_provenance", + "columnsFrom": ["document_id"], + "tableTo": "document", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "document_secret_provenance_status_check": { + "name": "document_secret_provenance_status_check", + "value": "\"document_secret_provenance\".\"status\" IN ('exact', 'unknown')" + } + }, + "isRLSEnabled": false + }, + "public.embedding": { + "name": "embedding", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chunk_index": { + "name": "chunk_index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "chunk_hash": { + "name": "chunk_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret_provenance_version": { + "name": "secret_provenance_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "content_length": { + "name": "content_length", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "embedding": { + "name": "embedding", + "type": "vector(1536)", + "primaryKey": false, + "notNull": false + }, + "embedding_384": { + "name": "embedding_384", + "type": "vector(384)", + "primaryKey": false, + "notNull": false + }, + "embedding_768": { + "name": "embedding_768", + "type": "vector(768)", + "primaryKey": false, + "notNull": false + }, + "embedding_1024": { + "name": "embedding_1024", + "type": "vector(1024)", + "primaryKey": false, + "notNull": false + }, + "embedding_3072": { + "name": "embedding_3072", + "type": "vector(3072)", + "primaryKey": false, + "notNull": false + }, + "embedding_model": { + "name": "embedding_model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text-embedding-3-small'" + }, + "start_offset": { + "name": "start_offset", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "end_offset": { + "name": "end_offset", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "tag1": { + "name": "tag1", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag2": { + "name": "tag2", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag3": { + "name": "tag3", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag4": { + "name": "tag4", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag5": { + "name": "tag5", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag6": { + "name": "tag6", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag7": { + "name": "tag7", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "number1": { + "name": "number1", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number2": { + "name": "number2", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number3": { + "name": "number3", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number4": { + "name": "number4", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number5": { + "name": "number5", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "date1": { + "name": "date1", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "date2": { + "name": "date2", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "boolean1": { + "name": "boolean1", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean2": { + "name": "boolean2", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean3": { + "name": "boolean3", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "content_tsv": { + "name": "content_tsv", + "type": "tsvector", + "primaryKey": false, + "notNull": false, + "generated": { + "type": "stored", + "as": "to_tsvector('english', \"embedding\".\"content\")" + } + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "emb_kb_id_idx": { + "name": "emb_kb_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "emb_doc_id_idx": { + "name": "emb_doc_id_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "emb_doc_chunk_idx": { + "name": "emb_doc_chunk_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chunk_index", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "emb_kb_model_idx": { + "name": "emb_kb_model_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "embedding_model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "emb_kb_enabled_idx": { + "name": "emb_kb_enabled_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "emb_doc_enabled_idx": { + "name": "emb_doc_enabled_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "embedding_vector_hnsw_idx": { + "name": "embedding_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "with": { + "m": 16, + "ef_construction": 64 + }, + "method": "hnsw", + "concurrently": false + }, + "embedding_384_vector_hnsw_idx": { + "name": "embedding_384_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding_384", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "with": { + "m": 16, + "ef_construction": 64 + }, + "method": "hnsw", + "concurrently": false + }, + "embedding_768_vector_hnsw_idx": { + "name": "embedding_768_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding_768", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "with": { + "m": 16, + "ef_construction": 64 + }, + "method": "hnsw", + "concurrently": false + }, + "embedding_1024_vector_hnsw_idx": { + "name": "embedding_1024_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding_1024", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "with": { + "m": 16, + "ef_construction": 64 + }, + "method": "hnsw", + "concurrently": false + }, + "embedding_3072_vector_hnsw_idx": { + "name": "embedding_3072_vector_hnsw_idx", + "columns": [ + { + "expression": "(\"embedding_3072\"::halfvec(3072)) halfvec_cosine_ops", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": { + "m": 16, + "ef_construction": 64 + }, + "method": "hnsw", + "concurrently": false + }, + "embedding_binary_hnsw_idx": { + "name": "embedding_binary_hnsw_idx", + "columns": [ + { + "expression": "(binary_quantize(\"embedding\")::bit(1536)) bit_hamming_ops", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": { + "m": 16, + "ef_construction": 64 + }, + "method": "hnsw", + "concurrently": false + }, + "embedding_384_binary_hnsw_idx": { + "name": "embedding_384_binary_hnsw_idx", + "columns": [ + { + "expression": "(binary_quantize(\"embedding_384\")::bit(384)) bit_hamming_ops", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": { + "m": 16, + "ef_construction": 64 + }, + "method": "hnsw", + "concurrently": false + }, + "embedding_768_binary_hnsw_idx": { + "name": "embedding_768_binary_hnsw_idx", + "columns": [ + { + "expression": "(binary_quantize(\"embedding_768\")::bit(768)) bit_hamming_ops", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": { + "m": 16, + "ef_construction": 64 + }, + "method": "hnsw", + "concurrently": false + }, + "embedding_1024_binary_hnsw_idx": { + "name": "embedding_1024_binary_hnsw_idx", + "columns": [ + { + "expression": "(binary_quantize(\"embedding_1024\")::bit(1024)) bit_hamming_ops", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": { + "m": 16, + "ef_construction": 64 + }, + "method": "hnsw", + "concurrently": false + }, + "embedding_3072_binary_hnsw_idx": { + "name": "embedding_3072_binary_hnsw_idx", + "columns": [ + { + "expression": "(binary_quantize(\"embedding_3072\")::bit(3072)) bit_hamming_ops", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": { + "m": 16, + "ef_construction": 64 + }, + "method": "hnsw", + "concurrently": false + }, + "emb_kb_tag1_lower_idx": { + "name": "emb_kb_tag1_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag1\")", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "emb_kb_tag2_lower_idx": { + "name": "emb_kb_tag2_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag2\")", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "emb_kb_tag3_lower_idx": { + "name": "emb_kb_tag3_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag3\")", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "emb_kb_tag4_lower_idx": { + "name": "emb_kb_tag4_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag4\")", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "emb_kb_tag5_lower_idx": { + "name": "emb_kb_tag5_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag5\")", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "emb_kb_tag6_lower_idx": { + "name": "emb_kb_tag6_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag6\")", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "emb_kb_tag7_lower_idx": { + "name": "emb_kb_tag7_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag7\")", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "emb_number1_idx": { + "name": "emb_number1_idx", + "columns": [ + { + "expression": "number1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "emb_number2_idx": { + "name": "emb_number2_idx", + "columns": [ + { + "expression": "number2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "emb_number3_idx": { + "name": "emb_number3_idx", + "columns": [ + { + "expression": "number3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "emb_number4_idx": { + "name": "emb_number4_idx", + "columns": [ + { + "expression": "number4", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "emb_number5_idx": { + "name": "emb_number5_idx", + "columns": [ + { + "expression": "number5", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "emb_date1_idx": { + "name": "emb_date1_idx", + "columns": [ + { + "expression": "date1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "emb_date2_idx": { + "name": "emb_date2_idx", + "columns": [ + { + "expression": "date2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "emb_boolean1_idx": { + "name": "emb_boolean1_idx", + "columns": [ + { + "expression": "boolean1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "emb_boolean2_idx": { + "name": "emb_boolean2_idx", + "columns": [ + { + "expression": "boolean2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "emb_boolean3_idx": { + "name": "emb_boolean3_idx", + "columns": [ + { + "expression": "boolean3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "emb_content_fts_idx": { + "name": "emb_content_fts_idx", + "columns": [ + { + "expression": "content_tsv", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "gin", + "concurrently": false + } + }, + "foreignKeys": { + "embedding_knowledge_base_id_knowledge_base_id_fk": { + "name": "embedding_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "embedding", + "columnsFrom": ["knowledge_base_id"], + "tableTo": "knowledge_base", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "embedding_document_id_document_id_fk": { + "name": "embedding_document_id_document_id_fk", + "tableFrom": "embedding", + "columnsFrom": ["document_id"], + "tableTo": "document", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "embedding_width_check": { + "name": "embedding_width_check", + "value": "num_nonnulls(\"embedding\", \"embedding_384\", \"embedding_768\", \"embedding_1024\", \"embedding_3072\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.embedding_keyword_search": { + "name": "embedding_keyword_search", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "content_tsv": { + "name": "content_tsv", + "type": "tsvector", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "embedding_keyword_search_kb_idx": { + "name": "embedding_keyword_search_kb_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "embedding_keyword_search_document_idx": { + "name": "embedding_keyword_search_document_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "embedding_keyword_search_content_idx": { + "name": "embedding_keyword_search_content_idx", + "columns": [ + { + "expression": "content_tsv", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "gin", + "concurrently": false + } + }, + "foreignKeys": { + "embedding_keyword_search_id_embedding_id_fk": { + "name": "embedding_keyword_search_id_embedding_id_fk", + "tableFrom": "embedding_keyword_search", + "columnsFrom": ["id"], + "tableTo": "embedding", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.embedding_search": { + "name": "embedding_search", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "binary": { + "name": "binary", + "type": "bit(1536)", + "primaryKey": false, + "notNull": false + }, + "binary_384": { + "name": "binary_384", + "type": "bit(384)", + "primaryKey": false, + "notNull": false + }, + "binary_768": { + "name": "binary_768", + "type": "bit(768)", + "primaryKey": false, + "notNull": false + }, + "binary_1024": { + "name": "binary_1024", + "type": "bit(1024)", + "primaryKey": false, + "notNull": false + }, + "binary_3072": { + "name": "binary_3072", + "type": "bit(3072)", + "primaryKey": false, + "notNull": false + }, + "vector": { + "name": "vector", + "type": "halfvec(1536)", + "primaryKey": false, + "notNull": false + }, + "vector_384": { + "name": "vector_384", + "type": "halfvec(384)", + "primaryKey": false, + "notNull": false + }, + "vector_512": { + "name": "vector_512", + "type": "halfvec(512)", + "primaryKey": false, + "notNull": false + }, + "vector_768": { + "name": "vector_768", + "type": "halfvec(768)", + "primaryKey": false, + "notNull": false + }, + "vector_1024": { + "name": "vector_1024", + "type": "halfvec(1024)", + "primaryKey": false, + "notNull": false + }, + "vector_3072": { + "name": "vector_3072", + "type": "halfvec(3072)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "embedding_search_kb_idx": { + "name": "embedding_search_kb_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "embedding_search_document_lookup_idx": { + "name": "embedding_search_document_lookup_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"embedding_search\".\"enabled\"", + "concurrently": true + }, + "embedding_search_binary_hnsw_idx": { + "name": "embedding_search_binary_hnsw_idx", + "columns": [ + { + "expression": "binary", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "bit_hamming_ops" + } + ], + "isUnique": false, + "with": { + "m": 16, + "ef_construction": 64 + }, + "method": "hnsw", + "concurrently": false + }, + "embedding_search_384_binary_hnsw_idx": { + "name": "embedding_search_384_binary_hnsw_idx", + "columns": [ + { + "expression": "binary_384", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "bit_hamming_ops" + } + ], + "isUnique": false, + "with": { + "m": 16, + "ef_construction": 64 + }, + "method": "hnsw", + "concurrently": false + }, + "embedding_search_768_binary_hnsw_idx": { + "name": "embedding_search_768_binary_hnsw_idx", + "columns": [ + { + "expression": "binary_768", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "bit_hamming_ops" + } + ], + "isUnique": false, + "with": { + "m": 16, + "ef_construction": 64 + }, + "method": "hnsw", + "concurrently": false + }, + "embedding_search_1024_binary_hnsw_idx": { + "name": "embedding_search_1024_binary_hnsw_idx", + "columns": [ + { + "expression": "binary_1024", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "bit_hamming_ops" + } + ], + "isUnique": false, + "with": { + "m": 16, + "ef_construction": 64 + }, + "method": "hnsw", + "concurrently": false + }, + "embedding_search_3072_binary_hnsw_idx": { + "name": "embedding_search_3072_binary_hnsw_idx", + "columns": [ + { + "expression": "binary_3072", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "bit_hamming_ops" + } + ], + "isUnique": false, + "with": { + "m": 16, + "ef_construction": 64 + }, + "method": "hnsw", + "concurrently": false + }, + "embedding_search_cosine_hnsw_idx": { + "name": "embedding_search_cosine_hnsw_idx", + "columns": [ + { + "expression": "vector", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "halfvec_cosine_ops" + } + ], + "isUnique": false, + "with": { + "m": 16, + "ef_construction": 64 + }, + "method": "hnsw", + "concurrently": false + }, + "embedding_search_512_cosine_hnsw_idx": { + "name": "embedding_search_512_cosine_hnsw_idx", + "columns": [ + { + "expression": "vector_512", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "halfvec_cosine_ops" + } + ], + "isUnique": false, + "with": { + "m": 16, + "ef_construction": 64 + }, + "method": "hnsw", + "concurrently": false + }, + "embedding_search_384_cosine_hnsw_idx": { + "name": "embedding_search_384_cosine_hnsw_idx", + "columns": [ + { + "expression": "vector_384", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "halfvec_cosine_ops" + } + ], + "isUnique": false, + "with": { + "m": 16, + "ef_construction": 64 + }, + "method": "hnsw", + "concurrently": false + }, + "embedding_search_768_cosine_hnsw_idx": { + "name": "embedding_search_768_cosine_hnsw_idx", + "columns": [ + { + "expression": "vector_768", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "halfvec_cosine_ops" + } + ], + "isUnique": false, + "with": { + "m": 16, + "ef_construction": 64 + }, + "method": "hnsw", + "concurrently": false + }, + "embedding_search_1024_cosine_hnsw_idx": { + "name": "embedding_search_1024_cosine_hnsw_idx", + "columns": [ + { + "expression": "vector_1024", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "halfvec_cosine_ops" + } + ], + "isUnique": false, + "with": { + "m": 16, + "ef_construction": 64 + }, + "method": "hnsw", + "concurrently": false + }, + "embedding_search_3072_cosine_hnsw_idx": { + "name": "embedding_search_3072_cosine_hnsw_idx", + "columns": [ + { + "expression": "vector_3072", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "halfvec_cosine_ops" + } + ], + "isUnique": false, + "with": { + "m": 16, + "ef_construction": 64 + }, + "method": "hnsw", + "concurrently": false + } + }, + "foreignKeys": { + "embedding_search_id_embedding_id_fk": { + "name": "embedding_search_id_embedding_id_fk", + "tableFrom": "embedding_search", + "columnsFrom": ["id"], + "tableTo": "embedding", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "embedding_search_width_check": { + "name": "embedding_search_width_check", + "value": "num_nonnulls(\"binary\", \"binary_384\", \"binary_768\", \"binary_1024\", \"binary_3072\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.embedding_secret_provenance": { + "name": "embedding_secret_provenance", + "schema": "", + "columns": { + "embedding_id": { + "name": "embedding_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entries": { + "name": "entries", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "embedding_secret_provenance_embedding_id_embedding_id_fk": { + "name": "embedding_secret_provenance_embedding_id_embedding_id_fk", + "tableFrom": "embedding_secret_provenance", + "columnsFrom": ["embedding_id"], + "tableTo": "embedding", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "embedding_secret_provenance_status_check": { + "name": "embedding_secret_provenance_status_check", + "value": "\"embedding_secret_provenance\".\"status\" IN ('exact', 'unknown')" + } + }, + "isRLSEnabled": false + }, + "public.environment": { + "name": "environment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "variables": { + "name": "variables", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "environment_user_id_user_id_fk": { + "name": "environment_user_id_user_id_fk", + "tableFrom": "environment", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "environment_user_id_unique": { + "name": "environment_user_id_unique", + "columns": ["user_id"], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_large_value_dependencies": { + "name": "execution_large_value_dependencies", + "schema": "", + "columns": { + "parent_key": { + "name": "parent_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "child_key": { + "name": "child_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_large_value_dependencies_workspace_parent_key_idx": { + "name": "execution_large_value_dependencies_workspace_parent_key_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "execution_large_value_dependencies_workspace_child_key_idx": { + "name": "execution_large_value_dependencies_workspace_child_key_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "child_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "execution_large_value_dependencies_workspace_id_workspace_id_fk": { + "name": "execution_large_value_dependencies_workspace_id_workspace_id_fk", + "tableFrom": "execution_large_value_dependencies", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "execution_large_value_dependencies_parent_key_child_key_pk": { + "name": "execution_large_value_dependencies_parent_key_child_key_pk", + "columns": ["parent_key", "child_key"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_large_value_references": { + "name": "execution_large_value_references", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "execution_large_value_reference_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_large_value_references_workspace_execution_source_idx": { + "name": "execution_large_value_references_workspace_execution_source_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "execution_large_value_references_workflow_id_idx": { + "name": "execution_large_value_references_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "execution_large_value_references_workspace_id_workspace_id_fk": { + "name": "execution_large_value_references_workspace_id_workspace_id_fk", + "tableFrom": "execution_large_value_references", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "execution_large_value_references_workflow_id_workflow_id_fk": { + "name": "execution_large_value_references_workflow_id_workflow_id_fk", + "tableFrom": "execution_large_value_references", + "columnsFrom": ["workflow_id"], + "tableTo": "workflow", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": { + "execution_large_value_references_key_execution_id_source_pk": { + "name": "execution_large_value_references_key_execution_id_source_pk", + "columns": ["key", "execution_id", "source"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_large_values": { + "name": "execution_large_values", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_execution_id": { + "name": "owner_execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "execution_large_values_owner_execution_id_idx": { + "name": "execution_large_values_owner_execution_id_idx", + "columns": [ + { + "expression": "owner_execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "execution_large_values_cleanup_idx": { + "name": "execution_large_values_cleanup_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"execution_large_values\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "execution_large_values_tombstone_cleanup_idx": { + "name": "execution_large_values_tombstone_cleanup_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"execution_large_values\".\"deleted_at\" IS NOT NULL", + "concurrently": false + }, + "execution_large_values_workflow_id_idx": { + "name": "execution_large_values_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "execution_large_values_workspace_id_workspace_id_fk": { + "name": "execution_large_values_workspace_id_workspace_id_fk", + "tableFrom": "execution_large_values", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "execution_large_values_workflow_id_workflow_id_fk": { + "name": "execution_large_values_workflow_id_workflow_id_fk", + "tableFrom": "execution_large_values", + "columnsFrom": ["workflow_id"], + "tableTo": "workflow", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.folder": { + "name": "folder", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "folder_resource_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "locked": { + "name": "locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "folder_user_idx": { + "name": "folder_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "folder_workspace_resource_parent_idx": { + "name": "folder_workspace_resource_parent_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "folder_parent_sort_idx": { + "name": "folder_parent_sort_idx", + "columns": [ + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "folder_deleted_at_idx": { + "name": "folder_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "folder_workspace_deleted_partial_idx": { + "name": "folder_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"folder\".\"deleted_at\" IS NOT NULL", + "concurrently": false + }, + "folder_workspace_resource_parent_name_active_unique": { + "name": "folder_workspace_resource_parent_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"parent_id\", '')", + "isExpression": true, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"folder\".\"deleted_at\" IS NULL", + "concurrently": false + } + }, + "foreignKeys": { + "folder_user_id_user_id_fk": { + "name": "folder_user_id_user_id_fk", + "tableFrom": "folder", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "folder_workspace_id_workspace_id_fk": { + "name": "folder_workspace_id_workspace_id_fk", + "tableFrom": "folder", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "folder_parent_id_folder_id_fk": { + "name": "folder_parent_id_folder_id_fk", + "tableFrom": "folder", + "columnsFrom": ["parent_id"], + "tableTo": "folder", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.idempotency_key": { + "name": "idempotency_key", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "result": { + "name": "result", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idempotency_key_created_at_idx": { + "name": "idempotency_key_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invitation": { + "name": "invitation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "invitation_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'organization'" + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "inviter_id": { + "name": "inviter_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "membership_intent": { + "name": "membership_intent", + "type": "invitation_membership_intent", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'internal'" + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "invitation_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "invitation_email_idx": { + "name": "invitation_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "invitation_organization_id_idx": { + "name": "invitation_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "invitation_status_idx": { + "name": "invitation_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "invitation_pending_email_org_unique": { + "name": "invitation_pending_email_org_unique", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"invitation\".\"status\" = 'pending' AND \"invitation\".\"organization_id\" IS NOT NULL", + "concurrently": false + } + }, + "foreignKeys": { + "invitation_inviter_id_user_id_fk": { + "name": "invitation_inviter_id_user_id_fk", + "tableFrom": "invitation", + "columnsFrom": ["inviter_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "invitation_organization_id_organization_id_fk": { + "name": "invitation_organization_id_organization_id_fk", + "tableFrom": "invitation", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "invitation_token_unique": { + "name": "invitation_token_unique", + "columns": ["token"], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invitation_workspace_grant": { + "name": "invitation_workspace_grant", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "invitation_id": { + "name": "invitation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission": { + "name": "permission", + "type": "permission_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "invitation_workspace_grant_unique": { + "name": "invitation_workspace_grant_unique", + "columns": [ + { + "expression": "invitation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "invitation_workspace_grant_workspace_id_idx": { + "name": "invitation_workspace_grant_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "invitation_workspace_grant_invitation_id_invitation_id_fk": { + "name": "invitation_workspace_grant_invitation_id_invitation_id_fk", + "tableFrom": "invitation_workspace_grant", + "columnsFrom": ["invitation_id"], + "tableTo": "invitation", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "invitation_workspace_grant_workspace_id_workspace_id_fk": { + "name": "invitation_workspace_grant_workspace_id_workspace_id_fk", + "tableFrom": "invitation_workspace_grant", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.job_execution_logs": { + "name": "job_execution_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "schedule_id": { + "name": "schedule_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "total_duration_ms": { + "name": "total_duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "execution_data": { + "name": "execution_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "cost": { + "name": "cost", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "job_execution_logs_schedule_id_idx": { + "name": "job_execution_logs_schedule_id_idx", + "columns": [ + { + "expression": "schedule_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "job_execution_logs_workspace_started_at_idx": { + "name": "job_execution_logs_workspace_started_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "job_execution_logs_workspace_ended_at_id_idx": { + "name": "job_execution_logs_workspace_ended_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"ended_at\")", + "isExpression": true, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "job_execution_logs_execution_id_unique": { + "name": "job_execution_logs_execution_id_unique", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "job_execution_logs_trigger_idx": { + "name": "job_execution_logs_trigger_idx", + "columns": [ + { + "expression": "trigger", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "job_execution_logs_schedule_id_workflow_schedule_id_fk": { + "name": "job_execution_logs_schedule_id_workflow_schedule_id_fk", + "tableFrom": "job_execution_logs", + "columnsFrom": ["schedule_id"], + "tableTo": "workflow_schedule", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + }, + "job_execution_logs_workspace_id_workspace_id_fk": { + "name": "job_execution_logs_workspace_id_workspace_id_fk", + "tableFrom": "job_execution_logs", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_base": { + "name": "knowledge_base", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "folder_id": { + "name": "folder_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_search_index": { + "name": "is_search_index", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "embedding_model": { + "name": "embedding_model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text-embedding-3-small'" + }, + "embedding_dimension": { + "name": "embedding_dimension", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1536 + }, + "chunking_config": { + "name": "chunking_config", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{\"maxSize\": 1024, \"minSize\": 1, \"overlap\": 200}'" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "kb_organization_id_idx": { + "name": "kb_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "kb_organization_search_index_unique": { + "name": "kb_organization_search_index_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"knowledge_base\".\"is_search_index\" = true AND \"knowledge_base\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "kb_organization_name_active_unique": { + "name": "kb_organization_name_active_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"knowledge_base\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "kb_user_id_idx": { + "name": "kb_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "kb_workspace_id_idx": { + "name": "kb_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "kb_user_workspace_idx": { + "name": "kb_user_workspace_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "kb_folder_id_idx": { + "name": "kb_folder_id_idx", + "columns": [ + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "kb_deleted_at_idx": { + "name": "kb_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "kb_workspace_deleted_partial_idx": { + "name": "kb_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"knowledge_base\".\"deleted_at\" IS NOT NULL", + "concurrently": false + }, + "kb_workspace_name_active_unique": { + "name": "kb_workspace_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"knowledge_base\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "kb_workspace_search_index_unique": { + "name": "kb_workspace_search_index_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"knowledge_base\".\"is_search_index\" = true AND \"knowledge_base\".\"deleted_at\" IS NULL", + "concurrently": false + } + }, + "foreignKeys": { + "knowledge_base_user_id_user_id_fk": { + "name": "knowledge_base_user_id_user_id_fk", + "tableFrom": "knowledge_base", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "knowledge_base_workspace_id_workspace_id_fk": { + "name": "knowledge_base_workspace_id_workspace_id_fk", + "tableFrom": "knowledge_base", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "knowledge_base_organization_id_organization_id_fk": { + "name": "knowledge_base_organization_id_organization_id_fk", + "tableFrom": "knowledge_base", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "knowledge_base_folder_id_folder_id_fk": { + "name": "knowledge_base_folder_id_folder_id_fk", + "tableFrom": "knowledge_base", + "columnsFrom": ["folder_id"], + "tableTo": "folder", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kb_owner_check": { + "name": "kb_owner_check", + "value": "num_nonnulls(\"knowledge_base\".\"workspace_id\", \"knowledge_base\".\"organization_id\") = 1" + }, + "kb_organization_search_index_check": { + "name": "kb_organization_search_index_check", + "value": "\"knowledge_base\".\"organization_id\" IS NULL OR \"knowledge_base\".\"is_search_index\"" + }, + "kb_organization_folder_check": { + "name": "kb_organization_folder_check", + "value": "\"knowledge_base\".\"organization_id\" IS NULL OR \"knowledge_base\".\"folder_id\" IS NULL" + } + }, + "isRLSEnabled": false + }, + "public.knowledge_base_tag_definitions": { + "name": "knowledge_base_tag_definitions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tag_slot": { + "name": "tag_slot", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "field_type": { + "name": "field_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "kb_tag_definitions_kb_slot_idx": { + "name": "kb_tag_definitions_kb_slot_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tag_slot", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "kb_tag_definitions_kb_display_name_idx": { + "name": "kb_tag_definitions_kb_display_name_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "kb_tag_definitions_kb_id_idx": { + "name": "kb_tag_definitions_kb_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "knowledge_base_tag_definitions_knowledge_base_id_knowledge_base_id_fk": { + "name": "knowledge_base_tag_definitions_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "knowledge_base_tag_definitions", + "columnsFrom": ["knowledge_base_id"], + "tableTo": "knowledge_base", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_connector": { + "name": "knowledge_connector", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connector_type": { + "name": "connector_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_api_key": { + "name": "encrypted_api_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_config": { + "name": "source_config", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "sync_mode": { + "name": "sync_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'full'" + }, + "sync_interval_minutes": { + "name": "sync_interval_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1440 + }, + "access_mode": { + "name": "access_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'workspace'" + }, + "credential_group_id": { + "name": "credential_group_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_group_option_id": { + "name": "credential_group_option_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "member_sync_status": { + "name": "member_sync_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "member_sync_lock_token": { + "name": "member_sync_lock_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "member_sync_lock_lease_at": { + "name": "member_sync_lock_lease_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "next_member_sync_at": { + "name": "next_member_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_member_sync_at": { + "name": "last_member_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_member_sync_error": { + "name": "last_member_sync_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "member_sync_consecutive_failures": { + "name": "member_sync_consecutive_failures", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "access_rewrite_pending": { + "name": "access_rewrite_pending", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_sync_error": { + "name": "last_sync_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_sync_doc_count": { + "name": "last_sync_doc_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "listing_checkpoint": { + "name": "listing_checkpoint", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "directory_checkpoint": { + "name": "directory_checkpoint", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "next_sync_at": { + "name": "next_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "next_directory_sync_at": { + "name": "next_directory_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "consecutive_failures": { + "name": "consecutive_failures", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "sync_lock_token": { + "name": "sync_lock_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sync_lock_lease_at": { + "name": "sync_lock_lease_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "kc_knowledge_base_id_idx": { + "name": "kc_knowledge_base_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "kc_status_next_sync_idx": { + "name": "kc_status_next_sync_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_sync_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "kc_archived_at_partial_idx": { + "name": "kc_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"knowledge_connector\".\"archived_at\" IS NOT NULL", + "concurrently": false + }, + "kc_deleted_at_partial_idx": { + "name": "kc_deleted_at_partial_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"knowledge_connector\".\"deleted_at\" IS NOT NULL", + "concurrently": false + }, + "kc_member_sync_due_idx": { + "name": "kc_member_sync_due_idx", + "columns": [ + { + "expression": "member_sync_status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_member_sync_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"knowledge_connector\".\"access_mode\" = 'members' AND \"knowledge_connector\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "kc_directory_sync_due_idx": { + "name": "kc_directory_sync_due_idx", + "columns": [ + { + "expression": "next_directory_sync_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"knowledge_connector\".\"access_mode\" = 'admin' AND \"knowledge_connector\".\"deleted_at\" IS NULL", + "concurrently": false + } + }, + "foreignKeys": { + "knowledge_connector_knowledge_base_id_knowledge_base_id_fk": { + "name": "knowledge_connector_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "knowledge_connector", + "columnsFrom": ["knowledge_base_id"], + "tableTo": "knowledge_base", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "knowledge_connector_credential_group_id_credential_group_id_fk": { + "name": "knowledge_connector_credential_group_id_credential_group_id_fk", + "tableFrom": "knowledge_connector", + "columnsFrom": ["credential_group_id"], + "tableTo": "credential_group", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kc_access_mode_check": { + "name": "kc_access_mode_check", + "value": "\"knowledge_connector\".\"access_mode\" IN ('workspace', 'members', 'admin')" + }, + "kc_member_sync_status_check": { + "name": "kc_member_sync_status_check", + "value": "\"knowledge_connector\".\"member_sync_status\" IN ('idle', 'pending', 'running', 'error', 'disabled')" + }, + "kc_sync_lock_exclusive_check": { + "name": "kc_sync_lock_exclusive_check", + "value": "NOT (\"knowledge_connector\".\"sync_lock_token\" IS NOT NULL AND \"knowledge_connector\".\"member_sync_lock_token\" IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.knowledge_connector_member": { + "name": "knowledge_connector_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_token": { + "name": "subject_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "consecutive_failures": { + "name": "consecutive_failures", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_started_at": { + "name": "last_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_complete_listing_at": { + "name": "last_complete_listing_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_listed_count": { + "name": "last_listed_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "member_synced_through": { + "name": "member_synced_through", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "change_cursor": { + "name": "change_cursor", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "listing_checkpoint": { + "name": "listing_checkpoint", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "suspended_at": { + "name": "suspended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "kcm_organization_id_idx": { + "name": "kcm_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "kcm_connector_credential_unique": { + "name": "kcm_connector_credential_unique", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "kcm_connector_queue_idx": { + "name": "kcm_connector_queue_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "first" + }, + { + "expression": "last_started_at", + "isExpression": false, + "asc": true, + "nulls": "first" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "kcm_credential_idx": { + "name": "kcm_credential_idx", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "knowledge_connector_member_workspace_id_workspace_id_fk": { + "name": "knowledge_connector_member_workspace_id_workspace_id_fk", + "tableFrom": "knowledge_connector_member", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "knowledge_connector_member_organization_id_organization_id_fk": { + "name": "knowledge_connector_member_organization_id_organization_id_fk", + "tableFrom": "knowledge_connector_member", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "knowledge_connector_member_connector_id_knowledge_connector_id_fk": { + "name": "knowledge_connector_member_connector_id_knowledge_connector_id_fk", + "tableFrom": "knowledge_connector_member", + "columnsFrom": ["connector_id"], + "tableTo": "knowledge_connector", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "knowledge_connector_member_credential_id_credential_id_fk": { + "name": "knowledge_connector_member_credential_id_credential_id_fk", + "tableFrom": "knowledge_connector_member", + "columnsFrom": ["credential_id"], + "tableTo": "credential", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kcm_owner_check": { + "name": "kcm_owner_check", + "value": "num_nonnulls(\"knowledge_connector_member\".\"workspace_id\", \"knowledge_connector_member\".\"organization_id\") = 1" + }, + "kcm_status_check": { + "name": "kcm_status_check", + "value": "\"knowledge_connector_member\".\"status\" IN ('active', 'suspended', 'disabled')" + }, + "kcm_subject_token_shape_check": { + "name": "kcm_subject_token_shape_check", + "value": "\"knowledge_connector_member\".\"subject_token\" ~ '^s:[^:]+:[^:]+:.+$'" + } + }, + "isRLSEnabled": false + }, + "public.knowledge_connector_member_sync_log": { + "name": "knowledge_connector_member_sync_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "members_claimed": { + "name": "members_claimed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "members_completed": { + "name": "members_completed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "members_incomplete": { + "name": "members_incomplete", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "members_failed": { + "name": "members_failed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_failed": { + "name": "docs_failed", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "processing_dispatch_failed": { + "name": "processing_dispatch_failed", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "docs_listed": { + "name": "docs_listed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_added": { + "name": "docs_added", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_updated": { + "name": "docs_updated", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_unchanged": { + "name": "docs_unchanged", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_hydrated_once": { + "name": "docs_hydrated_once", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "observations_added": { + "name": "observations_added", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "observations_removed": { + "name": "observations_removed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_tombstoned": { + "name": "docs_tombstoned", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_resurrected": { + "name": "docs_resurrected", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_purged": { + "name": "docs_purged", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "credentials_audited": { + "name": "credentials_audited", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "kcmsl_connector_started_at_idx": { + "name": "kcmsl_connector_started_at_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"started_at\" DESC", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "kcmsl_started_at_partial_idx": { + "name": "kcmsl_started_at_partial_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"knowledge_connector_member_sync_log\".\"status\" = 'started'", + "concurrently": false + } + }, + "foreignKeys": { + "knowledge_connector_member_sync_log_connector_id_knowledge_connector_id_fk": { + "name": "knowledge_connector_member_sync_log_connector_id_knowledge_connector_id_fk", + "tableFrom": "knowledge_connector_member_sync_log", + "columnsFrom": ["connector_id"], + "tableTo": "knowledge_connector", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kcmsl_status_check": { + "name": "kcmsl_status_check", + "value": "\"knowledge_connector_member_sync_log\".\"status\" IN ('started', 'partial', 'completed', 'failed')" + } + }, + "isRLSEnabled": false + }, + "public.knowledge_connector_partition": { + "name": "knowledge_connector_partition", + "schema": "", + "columns": { + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "partition_key": { + "name": "partition_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "generation_id": { + "name": "generation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "context": { + "name": "context", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "cursor": { + "name": "cursor", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "retry_at": { + "name": "retry_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_served_at": { + "name": "last_served_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "failure": { + "name": "failure", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "permission_cursor": { + "name": "permission_cursor", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "permission_attempts": { + "name": "permission_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "permission_retry_at": { + "name": "permission_retry_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "permission_last_served_at": { + "name": "permission_last_served_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "permission_started_at": { + "name": "permission_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "permission_failure": { + "name": "permission_failure", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "kcp_content_due_idx": { + "name": "kcp_content_due_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "generation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "retry_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_served_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "kcp_permission_due_idx": { + "name": "kcp_permission_due_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "generation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_retry_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_last_served_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "knowledge_connector_partition_connector_id_knowledge_connector_id_fk": { + "name": "knowledge_connector_partition_connector_id_knowledge_connector_id_fk", + "tableFrom": "knowledge_connector_partition", + "columnsFrom": ["connector_id"], + "tableTo": "knowledge_connector", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "kcp_pk": { + "name": "kcp_pk", + "columns": ["connector_id", "partition_key"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kcp_partition_key_check": { + "name": "kcp_partition_key_check", + "value": "octet_length(\"knowledge_connector_partition\".\"partition_key\") BETWEEN 1 AND 1024" + }, + "kcp_context_check": { + "name": "kcp_context_check", + "value": "jsonb_typeof(\"knowledge_connector_partition\".\"context\") = 'object' AND octet_length(\"knowledge_connector_partition\".\"context\"::text) <= 16384" + }, + "kcp_status_check": { + "name": "kcp_status_check", + "value": "\"knowledge_connector_partition\".\"status\" IN ('pending', 'complete', 'blocked')" + }, + "kcp_cursor_check": { + "name": "kcp_cursor_check", + "value": "(\"knowledge_connector_partition\".\"cursor\" IS NULL OR octet_length(\"knowledge_connector_partition\".\"cursor\") <= 393216) AND (\"knowledge_connector_partition\".\"permission_cursor\" IS NULL OR octet_length(\"knowledge_connector_partition\".\"permission_cursor\") <= 393216)" + }, + "kcp_attempts_check": { + "name": "kcp_attempts_check", + "value": "\"knowledge_connector_partition\".\"attempts\" >= 0 AND \"knowledge_connector_partition\".\"permission_attempts\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.knowledge_connector_permission_grant": { + "name": "knowledge_connector_permission_grant", + "schema": "", + "columns": { + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "group_key": { + "name": "group_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_token": { + "name": "subject_token", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "kcpg_subject_idx": { + "name": "kcpg_subject_idx", + "columns": [ + { + "expression": "subject_token", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "group_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "kcpg_snapshot_fk": { + "name": "kcpg_snapshot_fk", + "tableFrom": "knowledge_connector_permission_grant", + "columnsFrom": ["connector_id"], + "tableTo": "knowledge_connector_permission_snapshot", + "columnsTo": ["connector_id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "kcpg_pk": { + "name": "kcpg_pk", + "columns": ["connector_id", "group_key", "subject_token"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kcpg_group_check": { + "name": "kcpg_group_check", + "value": "length(\"knowledge_connector_permission_grant\".\"group_key\") BETWEEN 1 AND 255" + }, + "kcpg_subject_check": { + "name": "kcpg_subject_check", + "value": "\"knowledge_connector_permission_grant\".\"subject_token\" ~ '^u:[^[:space:]A-Z]+@[^[:space:]A-Z]+$'" + } + }, + "isRLSEnabled": false + }, + "public.knowledge_connector_permission_snapshot": { + "name": "knowledge_connector_permission_snapshot", + "schema": "", + "columns": { + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "kcps_connector_fk": { + "name": "kcps_connector_fk", + "tableFrom": "knowledge_connector_permission_snapshot", + "columnsFrom": ["connector_id"], + "tableTo": "knowledge_connector", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kcps_revision_check": { + "name": "kcps_revision_check", + "value": "\"knowledge_connector_permission_snapshot\".\"revision\" > 0" + } + }, + "isRLSEnabled": false + }, + "public.knowledge_connector_sync_log": { + "name": "knowledge_connector_sync_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "docs_added": { + "name": "docs_added", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_updated": { + "name": "docs_updated", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_deleted": { + "name": "docs_deleted", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_unchanged": { + "name": "docs_unchanged", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_skipped": { + "name": "docs_skipped", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_failed": { + "name": "docs_failed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "listed_count": { + "name": "listed_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "kcsl_connector_started_at_idx": { + "name": "kcsl_connector_started_at_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"started_at\" DESC", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "kcsl_started_at_partial_idx": { + "name": "kcsl_started_at_partial_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"knowledge_connector_sync_log\".\"status\" = 'started'", + "concurrently": false + } + }, + "foreignKeys": { + "knowledge_connector_sync_log_connector_id_knowledge_connector_id_fk": { + "name": "knowledge_connector_sync_log_connector_id_knowledge_connector_id_fk", + "tableFrom": "knowledge_connector_sync_log", + "columnsFrom": ["connector_id"], + "tableTo": "knowledge_connector", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_document_observation": { + "name": "knowledge_document_observation", + "schema": "", + "columns": { + "document_id": { + "name": "document_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "member_id": { + "name": "member_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "kdo_member_idx": { + "name": "kdo_member_idx", + "columns": [ + { + "expression": "member_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "knowledge_document_observation_document_id_document_id_fk": { + "name": "knowledge_document_observation_document_id_document_id_fk", + "tableFrom": "knowledge_document_observation", + "columnsFrom": ["document_id"], + "tableTo": "document", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "knowledge_document_observation_member_id_knowledge_connector_member_id_fk": { + "name": "knowledge_document_observation_member_id_knowledge_connector_member_id_fk", + "tableFrom": "knowledge_document_observation", + "columnsFrom": ["member_id"], + "tableTo": "knowledge_connector_member", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "knowledge_document_observation_document_id_member_id_pk": { + "name": "knowledge_document_observation_document_id_member_id_pk", + "columns": ["document_id", "member_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_external_directory": { + "name": "knowledge_external_directory", + "schema": "", + "columns": { + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sync_lock_token": { + "name": "sync_lock_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sync_lock_lease_at": { + "name": "sync_lock_lease_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_started_at": { + "name": "last_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_complete_sync_at": { + "name": "last_complete_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "ked_organization_id_idx": { + "name": "ked_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "ked_workspace_identity_unique": { + "name": "ked_workspace_identity_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "ked_organization_identity_unique": { + "name": "ked_organization_identity_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "knowledge_external_directory_workspace_id_workspace_id_fk": { + "name": "knowledge_external_directory_workspace_id_workspace_id_fk", + "tableFrom": "knowledge_external_directory", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "knowledge_external_directory_organization_id_organization_id_fk": { + "name": "knowledge_external_directory_organization_id_organization_id_fk", + "tableFrom": "knowledge_external_directory", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "ked_owner_check": { + "name": "ked_owner_check", + "value": "num_nonnulls(\"knowledge_external_directory\".\"workspace_id\", \"knowledge_external_directory\".\"organization_id\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.knowledge_external_group": { + "name": "knowledge_external_group", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_group_id": { + "name": "external_group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "keg_organization_id_idx": { + "name": "keg_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "keg_organization_identity_unique": { + "name": "keg_organization_identity_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "keg_organization_synced_idx": { + "name": "keg_organization_synced_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_synced_at", + "isExpression": false, + "asc": true, + "nulls": "first" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "keg_identity_unique": { + "name": "keg_identity_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "keg_workspace_synced_idx": { + "name": "keg_workspace_synced_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_synced_at", + "isExpression": false, + "asc": true, + "nulls": "first" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "knowledge_external_group_organization_id_organization_id_fk": { + "name": "knowledge_external_group_organization_id_organization_id_fk", + "tableFrom": "knowledge_external_group", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "keg_workspace_fk": { + "name": "keg_workspace_fk", + "tableFrom": "knowledge_external_group", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "keg_owner_check": { + "name": "keg_owner_check", + "value": "num_nonnulls(\"knowledge_external_group\".\"workspace_id\", \"knowledge_external_group\".\"organization_id\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.knowledge_external_group_member": { + "name": "knowledge_external_group_member", + "schema": "", + "columns": { + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_token": { + "name": "subject_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "kegm_subject_token_idx": { + "name": "kegm_subject_token_idx", + "columns": [ + { + "expression": "subject_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "kegm_group_fk": { + "name": "kegm_group_fk", + "tableFrom": "knowledge_external_group_member", + "columnsFrom": ["group_id"], + "tableTo": "knowledge_external_group", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "knowledge_external_group_member_group_id_subject_token_pk": { + "name": "knowledge_external_group_member_group_id_subject_token_pk", + "columns": ["group_id", "subject_token"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_server_oauth": { + "name": "mcp_server_oauth", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "mcp_server_id": { + "name": "mcp_server_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_information": { + "name": "client_information", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tokens": { + "name": "tokens", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "code_verifier": { + "name": "code_verifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state_created_at": { + "name": "state_created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_refreshed_at": { + "name": "last_refreshed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mcp_server_oauth_server_unique": { + "name": "mcp_server_oauth_server_unique", + "columns": [ + { + "expression": "mcp_server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "mcp_server_oauth_state_idx": { + "name": "mcp_server_oauth_state_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "mcp_server_oauth_mcp_server_id_mcp_servers_id_fk": { + "name": "mcp_server_oauth_mcp_server_id_mcp_servers_id_fk", + "tableFrom": "mcp_server_oauth", + "columnsFrom": ["mcp_server_id"], + "tableTo": "mcp_servers", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "mcp_server_oauth_user_id_user_id_fk": { + "name": "mcp_server_oauth_user_id_user_id_fk", + "tableFrom": "mcp_server_oauth", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + }, + "mcp_server_oauth_workspace_id_workspace_id_fk": { + "name": "mcp_server_oauth_workspace_id_workspace_id_fk", + "tableFrom": "mcp_server_oauth", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "mcp_server_oauth_organization_id_organization_id_fk": { + "name": "mcp_server_oauth_organization_id_organization_id_fk", + "tableFrom": "mcp_server_oauth", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "mcp_server_oauth_owner_check": { + "name": "mcp_server_oauth_owner_check", + "value": "num_nonnulls(\"mcp_server_oauth\".\"workspace_id\", \"mcp_server_oauth\".\"organization_id\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.mcp_servers": { + "name": "mcp_servers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_group_id": { + "name": "credential_group_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "managed_connector_id": { + "name": "managed_connector_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oauth_config_version": { + "name": "oauth_config_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "transport": { + "name": "transport", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'headers'" + }, + "oauth_client_id": { + "name": "oauth_client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oauth_client_secret": { + "name": "oauth_client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "headers": { + "name": "headers", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "timeout": { + "name": "timeout", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 30000 + }, + "retries": { + "name": "retries", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3 + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_connected": { + "name": "last_connected", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "connection_status": { + "name": "connection_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'disconnected'" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_config": { + "name": "status_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "tool_count": { + "name": "tool_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "last_tools_refresh": { + "name": "last_tools_refresh", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "total_requests": { + "name": "total_requests", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "last_used": { + "name": "last_used", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mcp_servers_organization_id_idx": { + "name": "mcp_servers_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "mcp_servers_workspace_enabled_idx": { + "name": "mcp_servers_workspace_enabled_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "mcp_servers_credential_group_idx": { + "name": "mcp_servers_credential_group_idx", + "columns": [ + { + "expression": "credential_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "mcp_servers_credential_group_managed_connector_unique": { + "name": "mcp_servers_credential_group_managed_connector_unique", + "columns": [ + { + "expression": "credential_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "managed_connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"mcp_servers\".\"credential_group_id\" IS NOT NULL AND \"mcp_servers\".\"managed_connector_id\" IS NOT NULL AND \"mcp_servers\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "mcp_servers_workspace_deleted_partial_idx": { + "name": "mcp_servers_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"mcp_servers\".\"deleted_at\" IS NOT NULL", + "concurrently": false + } + }, + "foreignKeys": { + "mcp_servers_workspace_id_workspace_id_fk": { + "name": "mcp_servers_workspace_id_workspace_id_fk", + "tableFrom": "mcp_servers", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "mcp_servers_organization_id_organization_id_fk": { + "name": "mcp_servers_organization_id_organization_id_fk", + "tableFrom": "mcp_servers", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "mcp_servers_credential_group_id_credential_group_id_fk": { + "name": "mcp_servers_credential_group_id_credential_group_id_fk", + "tableFrom": "mcp_servers", + "columnsFrom": ["credential_group_id"], + "tableTo": "credential_group", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + }, + "mcp_servers_created_by_user_id_fk": { + "name": "mcp_servers_created_by_user_id_fk", + "tableFrom": "mcp_servers", + "columnsFrom": ["created_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "mcp_servers_owner_check": { + "name": "mcp_servers_owner_check", + "value": "num_nonnulls(\"mcp_servers\".\"workspace_id\", \"mcp_servers\".\"organization_id\") = 1" + }, + "mcp_servers_organization_managed_check": { + "name": "mcp_servers_organization_managed_check", + "value": "\"mcp_servers\".\"organization_id\" IS NULL OR \"mcp_servers\".\"credential_group_id\" IS NOT NULL" + }, + "mcp_servers_credential_group_managed_connector_check": { + "name": "mcp_servers_credential_group_managed_connector_check", + "value": "\"mcp_servers\".\"credential_group_id\" IS NULL OR \"mcp_servers\".\"managed_connector_id\" IS NOT NULL" + }, + "mcp_servers_managed_connector_oauth_check": { + "name": "mcp_servers_managed_connector_oauth_check", + "value": "\"mcp_servers\".\"managed_connector_id\" IS NULL OR \"mcp_servers\".\"auth_type\" = 'oauth'" + } + }, + "isRLSEnabled": false + }, + "public.member": { + "name": "member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "member_user_id_unique": { + "name": "member_user_id_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "member_organization_id_idx": { + "name": "member_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "member_user_id_user_id_fk": { + "name": "member_user_id_user_id_fk", + "tableFrom": "member", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "member_organization_id_organization_id_fk": { + "name": "member_organization_id_organization_id_fk", + "tableFrom": "member", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.memory": { + "name": "memory", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "secret_provenance_version": { + "name": "secret_provenance_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "memory_key_idx": { + "name": "memory_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "memory_workspace_idx": { + "name": "memory_workspace_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "memory_workspace_key_idx": { + "name": "memory_workspace_key_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "memory_workspace_deleted_partial_idx": { + "name": "memory_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"memory\".\"deleted_at\" IS NOT NULL", + "concurrently": false + } + }, + "foreignKeys": { + "memory_workspace_id_workspace_id_fk": { + "name": "memory_workspace_id_workspace_id_fk", + "tableFrom": "memory", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.memory_secret_provenance": { + "name": "memory_secret_provenance", + "schema": "", + "columns": { + "memory_id": { + "name": "memory_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entries": { + "name": "entries", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "memory_secret_provenance_memory_id_memory_id_fk": { + "name": "memory_secret_provenance_memory_id_memory_id_fk", + "tableFrom": "memory_secret_provenance", + "columnsFrom": ["memory_id"], + "tableTo": "memory", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "memory_secret_provenance_status_check": { + "name": "memory_secret_provenance_status_check", + "value": "\"memory_secret_provenance\".\"status\" IN ('exact', 'unknown')" + } + }, + "isRLSEnabled": false + }, + "public.mothership_inbox_allowed_sender": { + "name": "mothership_inbox_allowed_sender", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "added_by": { + "name": "added_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "inbox_sender_ws_email_idx": { + "name": "inbox_sender_ws_email_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "mothership_inbox_allowed_sender_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_allowed_sender_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_allowed_sender", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "mothership_inbox_allowed_sender_added_by_user_id_fk": { + "name": "mothership_inbox_allowed_sender_added_by_user_id_fk", + "tableFrom": "mothership_inbox_allowed_sender", + "columnsFrom": ["added_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_inbox_task": { + "name": "mothership_inbox_task", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_email": { + "name": "from_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_name": { + "name": "from_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body_preview": { + "name": "body_preview", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body_text": { + "name": "body_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body_html": { + "name": "body_html", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_message_id": { + "name": "email_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "in_reply_to": { + "name": "in_reply_to", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "response_message_id": { + "name": "response_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agentmail_message_id": { + "name": "agentmail_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "trigger_job_id": { + "name": "trigger_job_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_summary": { + "name": "result_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rejection_reason": { + "name": "rejection_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "has_attachments": { + "name": "has_attachments", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cc_recipients": { + "name": "cc_recipients", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "processing_started_at": { + "name": "processing_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "inbox_task_ws_created_at_idx": { + "name": "inbox_task_ws_created_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "inbox_task_ws_status_idx": { + "name": "inbox_task_ws_status_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "inbox_task_response_msg_id_idx": { + "name": "inbox_task_response_msg_id_idx", + "columns": [ + { + "expression": "response_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "inbox_task_email_msg_id_idx": { + "name": "inbox_task_email_msg_id_idx", + "columns": [ + { + "expression": "email_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "mothership_inbox_task_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_task_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_task", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "mothership_inbox_task_chat_id_copilot_chats_id_fk": { + "name": "mothership_inbox_task_chat_id_copilot_chats_id_fk", + "tableFrom": "mothership_inbox_task", + "columnsFrom": ["chat_id"], + "tableTo": "copilot_chats", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_inbox_webhook": { + "name": "mothership_inbox_webhook", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "webhook_id": { + "name": "webhook_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "mothership_inbox_webhook_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_webhook_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_webhook", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mothership_inbox_webhook_workspace_id_unique": { + "name": "mothership_inbox_webhook_workspace_id_unique", + "columns": ["workspace_id"], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_settings": { + "name": "mothership_settings", + "schema": "", + "columns": { + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "mcp_tool_refs": { + "name": "mcp_tool_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "custom_tool_refs": { + "name": "custom_tool_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "skill_refs": { + "name": "skill_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "mothership_settings_workspace_id_workspace_id_fk": { + "name": "mothership_settings_workspace_id_workspace_id_fk", + "tableFrom": "mothership_settings", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_access_token": { + "name": "oauth_access_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_id": { + "name": "refresh_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "resource": { + "name": "resource", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "oauth_access_token_client_id_idx": { + "name": "oauth_access_token_client_id_idx", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "oauth_access_token_session_id_idx": { + "name": "oauth_access_token_session_id_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "oauth_access_token_refresh_id_idx": { + "name": "oauth_access_token_refresh_id_idx", + "columns": [ + { + "expression": "refresh_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "oauth_access_token_user_client_idx": { + "name": "oauth_access_token_user_client_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "oauth_access_token_expires_at_idx": { + "name": "oauth_access_token_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "oauth_access_token_client_id_oauth_client_client_id_fk": { + "name": "oauth_access_token_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_access_token", + "columnsFrom": ["client_id"], + "tableTo": "oauth_client", + "columnsTo": ["client_id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "oauth_access_token_session_id_session_id_fk": { + "name": "oauth_access_token_session_id_session_id_fk", + "tableFrom": "oauth_access_token", + "columnsFrom": ["session_id"], + "tableTo": "session", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + }, + "oauth_access_token_user_id_user_id_fk": { + "name": "oauth_access_token_user_id_user_id_fk", + "tableFrom": "oauth_access_token", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "oauth_access_token_refresh_id_oauth_refresh_token_id_fk": { + "name": "oauth_access_token_refresh_id_oauth_refresh_token_id_fk", + "tableFrom": "oauth_access_token", + "columnsFrom": ["refresh_id"], + "tableTo": "oauth_refresh_token", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_access_token_token_unique": { + "name": "oauth_access_token_token_unique", + "columns": ["token"], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "oauth_access_token_search_resource_check": { + "name": "oauth_access_token_search_resource_check", + "value": "NOT ('search:read' = ANY(\"oauth_access_token\".\"scopes\")) OR \"oauth_access_token\".\"resource\" IS NOT NULL" + } + }, + "isRLSEnabled": false + }, + "public.oauth_client": { + "name": "oauth_client", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "disabled": { + "name": "disabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "skip_consent": { + "name": "skip_consent", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "enable_end_session": { + "name": "enable_end_session", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "subject_type": { + "name": "subject_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uri": { + "name": "uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "contacts": { + "name": "contacts", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "tos": { + "name": "tos", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "policy": { + "name": "policy", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_id": { + "name": "software_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_version": { + "name": "software_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_statement": { + "name": "software_statement", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redirect_uris": { + "name": "redirect_uris", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "post_logout_redirect_uris": { + "name": "post_logout_redirect_uris", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "token_endpoint_auth_method": { + "name": "token_endpoint_auth_method", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "grant_types": { + "name": "grant_types", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "response_types": { + "name": "response_types", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "require_pkce": { + "name": "require_pkce", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "oauth_client_user_id_idx": { + "name": "oauth_client_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "oauth_client_user_id_user_id_fk": { + "name": "oauth_client_user_id_user_id_fk", + "tableFrom": "oauth_client", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_client_client_id_unique": { + "name": "oauth_client_client_id_unique", + "columns": ["client_id"], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_consent": { + "name": "oauth_consent", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "oauth_consent_client_id_idx": { + "name": "oauth_consent_client_id_idx", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "oauth_consent_client_id_oauth_client_client_id_fk": { + "name": "oauth_consent_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_consent", + "columnsFrom": ["client_id"], + "tableTo": "oauth_client", + "columnsTo": ["client_id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "oauth_consent_user_id_user_id_fk": { + "name": "oauth_consent_user_id_user_id_fk", + "tableFrom": "oauth_consent", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_consent_user_client_reference_unique": { + "name": "oauth_consent_user_client_reference_unique", + "columns": ["user_id", "client_id", "reference_id"], + "nullsNotDistinct": true + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_refresh_token": { + "name": "oauth_refresh_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "revoked": { + "name": "revoked", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "auth_time": { + "name": "auth_time", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "resource": { + "name": "resource", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "family_id": { + "name": "family_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "oauth_refresh_token_client_id_idx": { + "name": "oauth_refresh_token_client_id_idx", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "oauth_refresh_token_session_id_idx": { + "name": "oauth_refresh_token_session_id_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "oauth_refresh_token_user_client_idx": { + "name": "oauth_refresh_token_user_client_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "oauth_refresh_token_expires_at_idx": { + "name": "oauth_refresh_token_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "oauth_refresh_token_client_id_oauth_client_client_id_fk": { + "name": "oauth_refresh_token_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_refresh_token", + "columnsFrom": ["client_id"], + "tableTo": "oauth_client", + "columnsTo": ["client_id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "oauth_refresh_token_session_id_session_id_fk": { + "name": "oauth_refresh_token_session_id_session_id_fk", + "tableFrom": "oauth_refresh_token", + "columnsFrom": ["session_id"], + "tableTo": "session", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + }, + "oauth_refresh_token_user_id_user_id_fk": { + "name": "oauth_refresh_token_user_id_user_id_fk", + "tableFrom": "oauth_refresh_token", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "oauth_refresh_token_family_id_oauth_token_family_id_fk": { + "name": "oauth_refresh_token_family_id_oauth_token_family_id_fk", + "tableFrom": "oauth_refresh_token", + "columnsFrom": ["family_id"], + "tableTo": "oauth_token_family", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_refresh_token_token_unique": { + "name": "oauth_refresh_token_token_unique", + "columns": ["token"], + "nullsNotDistinct": false + }, + "oauth_refresh_token_family_generation_unique": { + "name": "oauth_refresh_token_family_generation_unique", + "columns": ["family_id", "generation"], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "oauth_refresh_token_generation_check": { + "name": "oauth_refresh_token_generation_check", + "value": "\"oauth_refresh_token\".\"generation\" BETWEEN 0 AND 1000" + }, + "oauth_refresh_token_search_resource_check": { + "name": "oauth_refresh_token_search_resource_check", + "value": "NOT ('search:read' = ANY(\"oauth_refresh_token\".\"scopes\")) OR \"oauth_refresh_token\".\"resource\" IS NOT NULL" + } + }, + "isRLSEnabled": false + }, + "public.oauth_token_family": { + "name": "oauth_token_family", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "consent_id": { + "name": "consent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "current_generation": { + "name": "current_generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "oauth_token_family_client_id_idx": { + "name": "oauth_token_family_client_id_idx", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "oauth_token_family_session_id_idx": { + "name": "oauth_token_family_session_id_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "oauth_token_family_user_client_idx": { + "name": "oauth_token_family_user_client_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "oauth_token_family_consent_id_idx": { + "name": "oauth_token_family_consent_id_idx", + "columns": [ + { + "expression": "consent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "oauth_token_family_expires_at_idx": { + "name": "oauth_token_family_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "oauth_token_family_client_id_oauth_client_client_id_fk": { + "name": "oauth_token_family_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_token_family", + "columnsFrom": ["client_id"], + "tableTo": "oauth_client", + "columnsTo": ["client_id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "oauth_token_family_session_id_session_id_fk": { + "name": "oauth_token_family_session_id_session_id_fk", + "tableFrom": "oauth_token_family", + "columnsFrom": ["session_id"], + "tableTo": "session", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + }, + "oauth_token_family_user_id_user_id_fk": { + "name": "oauth_token_family_user_id_user_id_fk", + "tableFrom": "oauth_token_family", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "oauth_token_family_consent_id_oauth_consent_id_fk": { + "name": "oauth_token_family_consent_id_oauth_consent_id_fk", + "tableFrom": "oauth_token_family", + "columnsFrom": ["consent_id"], + "tableTo": "oauth_consent", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "oauth_token_family_generation_check": { + "name": "oauth_token_family_generation_check", + "value": "\"oauth_token_family\".\"current_generation\" BETWEEN 0 AND 1000" + } + }, + "isRLSEnabled": false + }, + "public.organization": { + "name": "organization", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "session_policy_settings": { + "name": "session_policy_settings", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "security_policy_version": { + "name": "security_policy_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "require_sso": { + "name": "require_sso", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "whitelabel_settings": { + "name": "whitelabel_settings", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "data_retention_settings": { + "name": "data_retention_settings", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "org_usage_limit": { + "name": "org_usage_limit", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "storage_used_bytes": { + "name": "storage_used_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "limit_notifications": { + "name": "limit_notifications", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "credit_balance": { + "name": "credit_balance", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_access_request_settings": { + "name": "organization_access_request_settings", + "schema": "", + "columns": { + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "allow_requests": { + "name": "allow_requests", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "organization_access_request_settings_organization_id_organization_id_fk": { + "name": "organization_access_request_settings_organization_id_organization_id_fk", + "tableFrom": "organization_access_request_settings", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "organization_access_request_settings_updated_by_user_id_fk": { + "name": "organization_access_request_settings_updated_by_user_id_fk", + "tableFrom": "organization_access_request_settings", + "columnsFrom": ["updated_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_byok_keys": { + "name": "organization_byok_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "encrypted_api_key": { + "name": "encrypted_api_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "organization_byok_organization_provider_idx": { + "name": "organization_byok_organization_provider_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "organization_byok_keys_organization_id_organization_id_fk": { + "name": "organization_byok_keys_organization_id_organization_id_fk", + "tableFrom": "organization_byok_keys", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "organization_byok_keys_created_by_user_id_fk": { + "name": "organization_byok_keys_created_by_user_id_fk", + "tableFrom": "organization_byok_keys", + "columnsFrom": ["created_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_member_usage_limit": { + "name": "organization_member_usage_limit", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "usage_limit": { + "name": "usage_limit", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "set_by": { + "name": "set_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "org_member_usage_limit_org_user_unique": { + "name": "org_member_usage_limit_org_user_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "org_member_usage_limit_organization_id_idx": { + "name": "org_member_usage_limit_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "organization_member_usage_limit_organization_id_organization_id_fk": { + "name": "organization_member_usage_limit_organization_id_organization_id_fk", + "tableFrom": "organization_member_usage_limit", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "organization_member_usage_limit_user_id_user_id_fk": { + "name": "organization_member_usage_limit_user_id_user_id_fk", + "tableFrom": "organization_member_usage_limit", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "organization_member_usage_limit_set_by_user_id_fk": { + "name": "organization_member_usage_limit_set_by_user_id_fk", + "tableFrom": "organization_member_usage_limit", + "columnsFrom": ["set_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_search_integration": { + "name": "organization_search_integration", + "schema": "", + "columns": { + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connector_type": { + "name": "connector_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "approved": { + "name": "approved", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "organization_search_integration_organization_id_organization_id_fk": { + "name": "organization_search_integration_organization_id_organization_id_fk", + "tableFrom": "organization_search_integration", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "organization_search_integration_organization_id_connector_type_pk": { + "name": "organization_search_integration_organization_id_connector_type_pk", + "columns": ["organization_id", "connector_type"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_search_invocation": { + "name": "organization_search_invocation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "surface": { + "name": "surface", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_types": { + "name": "source_types", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "result_count": { + "name": "result_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "organization_search_invocation_org_created_idx": { + "name": "organization_search_invocation_org_created_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "organization_search_invocation_user_idx": { + "name": "organization_search_invocation_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "organization_search_invocation_organization_id_organization_id_fk": { + "name": "organization_search_invocation_organization_id_organization_id_fk", + "tableFrom": "organization_search_invocation", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "organization_search_invocation_user_id_user_id_fk": { + "name": "organization_search_invocation_user_id_user_id_fk", + "tableFrom": "organization_search_invocation", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "organization_search_invocation_result_count_bounds": { + "name": "organization_search_invocation_result_count_bounds", + "value": "\"organization_search_invocation\".\"result_count\" BETWEEN 0 AND 100" + }, + "organization_search_invocation_source_types_bounds": { + "name": "organization_search_invocation_source_types_bounds", + "value": "cardinality(\"organization_search_invocation\".\"source_types\") <= 100" + } + }, + "isRLSEnabled": false + }, + "public.organization_search_mcp_invocation": { + "name": "organization_search_mcp_invocation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "auth_kind": { + "name": "auth_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oauth_client_id": { + "name": "oauth_client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "organization_search_mcp_invocation_org_created_idx": { + "name": "organization_search_mcp_invocation_org_created_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "organization_search_mcp_invocation_user_idx": { + "name": "organization_search_mcp_invocation_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "org_search_mcp_invocation_org_fk": { + "name": "org_search_mcp_invocation_org_fk", + "tableFrom": "organization_search_mcp_invocation", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "org_search_mcp_invocation_user_fk": { + "name": "org_search_mcp_invocation_user_fk", + "tableFrom": "organization_search_mcp_invocation", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "organization_search_mcp_invocation_tool_check": { + "name": "organization_search_mcp_invocation_tool_check", + "value": "\"organization_search_mcp_invocation\".\"tool_name\" IN ('search', 'read_document', 'chat')" + }, + "organization_search_mcp_invocation_outcome_check": { + "name": "organization_search_mcp_invocation_outcome_check", + "value": "\"organization_search_mcp_invocation\".\"outcome\" IN ('success', 'error', 'cancelled', 'rate_limited')" + }, + "organization_search_mcp_invocation_duration_check": { + "name": "organization_search_mcp_invocation_duration_check", + "value": "\"organization_search_mcp_invocation\".\"duration_ms\" >= 0" + }, + "organization_search_mcp_invocation_client_name_check": { + "name": "organization_search_mcp_invocation_client_name_check", + "value": "length(\"organization_search_mcp_invocation\".\"client_name\") <= 256" + }, + "organization_search_mcp_invocation_auth_check": { + "name": "organization_search_mcp_invocation_auth_check", + "value": "(\"organization_search_mcp_invocation\".\"auth_kind\" = 'oauth_access_token' AND \"organization_search_mcp_invocation\".\"oauth_client_id\" IS NOT NULL)\n OR (\"organization_search_mcp_invocation\".\"auth_kind\" IN ('personal_api_key', 'workspace_api_key') AND \"organization_search_mcp_invocation\".\"oauth_client_id\" IS NULL AND \"organization_search_mcp_invocation\".\"client_name\" IS NULL)" + } + }, + "isRLSEnabled": false + }, + "public.outbox_event": { + "name": "outbox_event", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "max_attempts": { + "name": "max_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10 + }, + "available_at": { + "name": "available_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "locked_at": { + "name": "locked_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "outbox_event_status_available_idx": { + "name": "outbox_event_status_available_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "available_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "outbox_event_pending_type_available_idx": { + "name": "outbox_event_pending_type_available_idx", + "columns": [ + { + "expression": "event_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "available_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"outbox_event\".\"status\" = 'pending'", + "concurrently": false + }, + "outbox_event_locked_at_idx": { + "name": "outbox_event_locked_at_idx", + "columns": [ + { + "expression": "locked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "outbox_event_type_created_idx": { + "name": "outbox_event_type_created_idx", + "columns": [ + { + "expression": "event_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.paused_executions": { + "name": "paused_executions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_snapshot": { + "name": "execution_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "pause_points": { + "name": "pause_points", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "total_pause_count": { + "name": "total_pause_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "resumed_count": { + "name": "resumed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "automatic_resume_retry_count": { + "name": "automatic_resume_retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paused'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "next_resume_at": { + "name": "next_resume_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "paused_executions_workflow_id_idx": { + "name": "paused_executions_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "paused_executions_status_idx": { + "name": "paused_executions_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "paused_executions_execution_id_unique": { + "name": "paused_executions_execution_id_unique", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "paused_executions_next_resume_at_idx": { + "name": "paused_executions_next_resume_at_idx", + "columns": [ + { + "expression": "next_resume_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "status = 'paused' AND next_resume_at IS NOT NULL", + "concurrently": false + } + }, + "foreignKeys": { + "paused_executions_workflow_id_workflow_id_fk": { + "name": "paused_executions_workflow_id_workflow_id_fk", + "tableFrom": "paused_executions", + "columnsFrom": ["workflow_id"], + "tableTo": "workflow", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pending_credential_draft": { + "name": "pending_credential_draft", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oauth_config": { + "name": "oauth_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pending_draft_organization_id_idx": { + "name": "pending_draft_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "pending_draft_user_provider_org": { + "name": "pending_draft_user_provider_org", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "pending_draft_user_provider_ws": { + "name": "pending_draft_user_provider_ws", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "pending_credential_draft_user_id_user_id_fk": { + "name": "pending_credential_draft_user_id_user_id_fk", + "tableFrom": "pending_credential_draft", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "pending_credential_draft_workspace_id_workspace_id_fk": { + "name": "pending_credential_draft_workspace_id_workspace_id_fk", + "tableFrom": "pending_credential_draft", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "pending_credential_draft_organization_id_organization_id_fk": { + "name": "pending_credential_draft_organization_id_organization_id_fk", + "tableFrom": "pending_credential_draft", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "pending_credential_draft_credential_id_credential_id_fk": { + "name": "pending_credential_draft_credential_id_credential_id_fk", + "tableFrom": "pending_credential_draft", + "columnsFrom": ["credential_id"], + "tableTo": "credential", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pending_draft_owner_check": { + "name": "pending_draft_owner_check", + "value": "num_nonnulls(\"pending_credential_draft\".\"workspace_id\", \"pending_credential_draft\".\"organization_id\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.permission_access_request": { + "name": "permission_access_request", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requester_id": { + "name": "requester_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope_key": { + "name": "scope_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_key": { + "name": "target_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target": { + "name": "target", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "target_label": { + "name": "target_label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "membership_id": { + "name": "membership_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "group_name": { + "name": "group_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "decision_reason": { + "name": "decision_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_by": { + "name": "decided_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decision": { + "name": "decision", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "decided_at": { + "name": "decided_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "permission_access_request_pending_unique": { + "name": "permission_access_request_pending_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requester_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"permission_access_request\".\"status\" = 'pending'", + "concurrently": false + }, + "permission_access_request_org_queue_idx": { + "name": "permission_access_request_org_queue_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "permission_access_request_requester_idx": { + "name": "permission_access_request_requester_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requester_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "permission_access_request_organization_id_organization_id_fk": { + "name": "permission_access_request_organization_id_organization_id_fk", + "tableFrom": "permission_access_request", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "permission_access_request_requester_id_user_id_fk": { + "name": "permission_access_request_requester_id_user_id_fk", + "tableFrom": "permission_access_request", + "columnsFrom": ["requester_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "permission_access_request_decided_by_user_id_fk": { + "name": "permission_access_request_decided_by_user_id_fk", + "tableFrom": "permission_access_request", + "columnsFrom": ["decided_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "permission_access_request_status_check": { + "name": "permission_access_request_status_check", + "value": "\"permission_access_request\".\"status\" in ('pending', 'fulfilled', 'declined', 'cancelled', 'closed')" + } + }, + "isRLSEnabled": false + }, + "public.permission_group": { + "name": "permission_group", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "membership_mode": { + "name": "membership_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'inherit'" + } + }, + "indexes": { + "permission_group_created_by_idx": { + "name": "permission_group_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "permission_group_organization_name_unique": { + "name": "permission_group_organization_name_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "permission_group_organization_default_unique": { + "name": "permission_group_organization_default_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "is_default = true", + "concurrently": false + } + }, + "foreignKeys": { + "permission_group_organization_id_organization_id_fk": { + "name": "permission_group_organization_id_organization_id_fk", + "tableFrom": "permission_group", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "permission_group_created_by_user_id_fk": { + "name": "permission_group_created_by_user_id_fk", + "tableFrom": "permission_group", + "columnsFrom": ["created_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permission_group_member": { + "name": "permission_group_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "permission_group_id": { + "name": "permission_group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "assigned_by": { + "name": "assigned_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assigned_at": { + "name": "assigned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "permission_group_member_group_id_idx": { + "name": "permission_group_member_group_id_idx", + "columns": [ + { + "expression": "permission_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "permission_group_member_group_user_unique": { + "name": "permission_group_member_group_user_unique", + "columns": [ + { + "expression": "permission_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "permission_group_member_organization_user_idx": { + "name": "permission_group_member_organization_user_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "permission_group_member_permission_group_id_permission_group_id_fk": { + "name": "permission_group_member_permission_group_id_permission_group_id_fk", + "tableFrom": "permission_group_member", + "columnsFrom": ["permission_group_id"], + "tableTo": "permission_group", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "permission_group_member_organization_id_organization_id_fk": { + "name": "permission_group_member_organization_id_organization_id_fk", + "tableFrom": "permission_group_member", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "permission_group_member_user_id_user_id_fk": { + "name": "permission_group_member_user_id_user_id_fk", + "tableFrom": "permission_group_member", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "permission_group_member_assigned_by_user_id_fk": { + "name": "permission_group_member_assigned_by_user_id_fk", + "tableFrom": "permission_group_member", + "columnsFrom": ["assigned_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permission_group_workspace": { + "name": "permission_group_workspace", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "permission_group_id": { + "name": "permission_group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "permission_group_workspace_workspace_id_idx": { + "name": "permission_group_workspace_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "permission_group_workspace_group_workspace_unique": { + "name": "permission_group_workspace_group_workspace_unique", + "columns": [ + { + "expression": "permission_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "permission_group_workspace_permission_group_id_permission_group_id_fk": { + "name": "permission_group_workspace_permission_group_id_permission_group_id_fk", + "tableFrom": "permission_group_workspace", + "columnsFrom": ["permission_group_id"], + "tableTo": "permission_group", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "permission_group_workspace_workspace_id_workspace_id_fk": { + "name": "permission_group_workspace_workspace_id_workspace_id_fk", + "tableFrom": "permission_group_workspace", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "permission_group_workspace_organization_id_organization_id_fk": { + "name": "permission_group_workspace_organization_id_organization_id_fk", + "tableFrom": "permission_group_workspace", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permissions": { + "name": "permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "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 + }, + "permission_type": { + "name": "permission_type", + "type": "permission_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "permissions_user_id_idx": { + "name": "permissions_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "permissions_entity_idx": { + "name": "permissions_entity_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "permissions_user_entity_type_idx": { + "name": "permissions_user_entity_type_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "permissions_user_entity_permission_idx": { + "name": "permissions_user_entity_permission_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "permissions_unique_constraint": { + "name": "permissions_unique_constraint", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "permissions_user_id_user_id_fk": { + "name": "permissions_user_id_user_id_fk", + "tableFrom": "permissions", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pinned_item": { + "name": "pinned_item", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pinned_at": { + "name": "pinned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pinned_item_user_workspace_idx": { + "name": "pinned_item_user_workspace_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "pinned_item_resource_idx": { + "name": "pinned_item_resource_idx", + "columns": [ + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "pinned_item_user_resource_unique": { + "name": "pinned_item_user_resource_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "pinned_item_user_id_user_id_fk": { + "name": "pinned_item_user_id_user_id_fk", + "tableFrom": "pinned_item", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "pinned_item_workspace_id_workspace_id_fk": { + "name": "pinned_item_workspace_id_workspace_id_fk", + "tableFrom": "pinned_item", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.public_share": { + "name": "public_share", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'public'" + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "allowed_emails": { + "name": "allowed_emails", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "public_share_token_unique": { + "name": "public_share_token_unique", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "public_share_resource_unique": { + "name": "public_share_resource_unique", + "columns": [ + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "public_share_resource_id_idx": { + "name": "public_share_resource_id_idx", + "columns": [ + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "public_share_workspace_id_idx": { + "name": "public_share_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "public_share_workspace_id_workspace_id_fk": { + "name": "public_share_workspace_id_workspace_id_fk", + "tableFrom": "public_share", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "public_share_created_by_user_id_fk": { + "name": "public_share_created_by_user_id_fk", + "tableFrom": "public_share", + "columnsFrom": ["created_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.rate_limit_bucket": { + "name": "rate_limit_bucket", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tokens": { + "name": "tokens", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "last_refill_at": { + "name": "last_refill_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "blocked_until": { + "name": "blocked_until", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "capacity_state": { + "name": "capacity_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.resource_policy": { + "name": "resource_policy", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "document": { + "name": "document", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "resource_policy_organization_id_idx": { + "name": "resource_policy_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "resource_policy_resource_unique": { + "name": "resource_policy_resource_unique", + "columns": [ + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "resource_policy_workspace_id_idx": { + "name": "resource_policy_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "resource_policy_workspace_id_workspace_id_fk": { + "name": "resource_policy_workspace_id_workspace_id_fk", + "tableFrom": "resource_policy", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "resource_policy_organization_id_organization_id_fk": { + "name": "resource_policy_organization_id_organization_id_fk", + "tableFrom": "resource_policy", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "resource_policy_created_by_user_id_fk": { + "name": "resource_policy_created_by_user_id_fk", + "tableFrom": "resource_policy", + "columnsFrom": ["created_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + }, + "resource_policy_updated_by_user_id_fk": { + "name": "resource_policy_updated_by_user_id_fk", + "tableFrom": "resource_policy", + "columnsFrom": ["updated_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "resource_policy_owner_check": { + "name": "resource_policy_owner_check", + "value": "num_nonnulls(\"resource_policy\".\"workspace_id\", \"resource_policy\".\"organization_id\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.resume_queue": { + "name": "resume_queue", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "paused_execution_id": { + "name": "paused_execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_execution_id": { + "name": "parent_execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "new_execution_id": { + "name": "new_execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "context_id": { + "name": "context_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resume_input": { + "name": "resume_input", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "queued_at": { + "name": "queued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "resume_queue_parent_status_idx": { + "name": "resume_queue_parent_status_idx", + "columns": [ + { + "expression": "parent_execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "resume_queue_new_execution_idx": { + "name": "resume_queue_new_execution_idx", + "columns": [ + { + "expression": "new_execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "resume_queue_paused_execution_id_paused_executions_id_fk": { + "name": "resume_queue_paused_execution_id_paused_executions_id_fk", + "tableFrom": "resume_queue", + "columnsFrom": ["paused_execution_id"], + "tableTo": "paused_executions", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sandbox_image": { + "name": "sandbox_image", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "spec_hash": { + "name": "spec_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "spec": { + "name": "spec", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "sandbox_image_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "image_ref": { + "name": "image_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_image_id": { + "name": "provider_image_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "build_id": { + "name": "build_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "materialization_generation": { + "name": "materialization_generation", + "type": "bigint", + "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 + }, + "error_detail": { + "name": "error_detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "sandbox_image_provider_spec_unique": { + "name": "sandbox_image_provider_spec_unique", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "spec_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "sandbox_image_status_idx": { + "name": "sandbox_image_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "sandbox_image_last_used_idx": { + "name": "sandbox_image_last_used_idx", + "columns": [ + { + "expression": "last_used_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_connection": { + "name": "scim_connection", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_request_at": { + "name": "last_request_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "reconcile_lock_token": { + "name": "reconcile_lock_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reconcile_lease_at": { + "name": "reconcile_lease_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "reconciled_at": { + "name": "reconciled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "scim_connection_organization_unique": { + "name": "scim_connection_organization_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "scim_connection_reconcile_due_idx": { + "name": "scim_connection_reconcile_due_idx", + "columns": [ + { + "expression": "reconciled_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "scim_connection_organization_id_organization_id_fk": { + "name": "scim_connection_organization_id_organization_id_fk", + "tableFrom": "scim_connection", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "scim_connection_created_by_user_id_fk": { + "name": "scim_connection_created_by_user_id_fk", + "tableFrom": "scim_connection", + "columnsFrom": ["created_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_credential": { + "name": "scim_credential", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_prefix": { + "name": "token_prefix", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scopes": { + "name": "scopes", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "revoked_by": { + "name": "revoked_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "scim_credential_token_hash_unique": { + "name": "scim_credential_token_hash_unique", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "scim_credential_connection_idx": { + "name": "scim_credential_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "scim_credential_connection_id_scim_connection_id_fk": { + "name": "scim_credential_connection_id_scim_connection_id_fk", + "tableFrom": "scim_credential", + "columnsFrom": ["connection_id"], + "tableTo": "scim_connection", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "scim_credential_revoked_by_user_id_fk": { + "name": "scim_credential_revoked_by_user_id_fk", + "tableFrom": "scim_credential", + "columnsFrom": ["revoked_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + }, + "scim_credential_created_by_user_id_fk": { + "name": "scim_credential_created_by_user_id_fk", + "tableFrom": "scim_credential", + "columnsFrom": ["created_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_group": { + "name": "scim_group", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name_key": { + "name": "display_name_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "order_key": { + "name": "order_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "scim_group_connection_display_name_unique": { + "name": "scim_group_connection_display_name_unique", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_name_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "scim_group_connection_external_id_unique": { + "name": "scim_group_connection_external_id_unique", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "external_id is not null", + "concurrently": false + }, + "scim_group_connection_order_idx": { + "name": "scim_group_connection_order_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "order_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "scim_group_connection_id_scim_connection_id_fk": { + "name": "scim_group_connection_id_scim_connection_id_fk", + "tableFrom": "scim_group", + "columnsFrom": ["connection_id"], + "tableTo": "scim_connection", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_group_mapping": { + "name": "scim_group_mapping", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_kind": { + "name": "target_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission_group_id": { + "name": "permission_group_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "permission_type": { + "name": "permission_type", + "type": "permission_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "scim_group_mapping_group_idx": { + "name": "scim_group_mapping_group_idx", + "columns": [ + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "scim_group_mapping_permission_group_idx": { + "name": "scim_group_mapping_permission_group_idx", + "columns": [ + { + "expression": "permission_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "scim_group_mapping_workspace_idx": { + "name": "scim_group_mapping_workspace_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "scim_group_mapping_group_target_unique": { + "name": "scim_group_mapping_group_target_unique", + "columns": [ + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"permission_group_id\", \"workspace_id\", \"role\")", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "scim_group_mapping_group_id_scim_group_id_fk": { + "name": "scim_group_mapping_group_id_scim_group_id_fk", + "tableFrom": "scim_group_mapping", + "columnsFrom": ["group_id"], + "tableTo": "scim_group", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "scim_group_mapping_permission_group_id_permission_group_id_fk": { + "name": "scim_group_mapping_permission_group_id_permission_group_id_fk", + "tableFrom": "scim_group_mapping", + "columnsFrom": ["permission_group_id"], + "tableTo": "permission_group", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "scim_group_mapping_workspace_id_workspace_id_fk": { + "name": "scim_group_mapping_workspace_id_workspace_id_fk", + "tableFrom": "scim_group_mapping", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "scim_group_mapping_created_by_user_id_fk": { + "name": "scim_group_mapping_created_by_user_id_fk", + "tableFrom": "scim_group_mapping", + "columnsFrom": ["created_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "scim_group_mapping_target_shape": { + "name": "scim_group_mapping_target_shape", + "value": "(\n (\"scim_group_mapping\".\"target_kind\" = 'permission_group' AND \"scim_group_mapping\".\"permission_group_id\" IS NOT NULL AND \"scim_group_mapping\".\"workspace_id\" IS NULL AND \"scim_group_mapping\".\"permission_type\" IS NULL AND \"scim_group_mapping\".\"role\" IS NULL)\n OR (\"scim_group_mapping\".\"target_kind\" = 'workspace' AND \"scim_group_mapping\".\"workspace_id\" IS NOT NULL AND \"scim_group_mapping\".\"permission_type\" IS NOT NULL AND \"scim_group_mapping\".\"permission_group_id\" IS NULL AND \"scim_group_mapping\".\"role\" IS NULL)\n OR (\"scim_group_mapping\".\"target_kind\" = 'org_role' AND \"scim_group_mapping\".\"role\" IS NOT NULL AND \"scim_group_mapping\".\"permission_group_id\" IS NULL AND \"scim_group_mapping\".\"workspace_id\" IS NULL AND \"scim_group_mapping\".\"permission_type\" IS NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.scim_group_member": { + "name": "scim_group_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scim_user_id": { + "name": "scim_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "scim_group_member_group_user_unique": { + "name": "scim_group_member_group_user_unique", + "columns": [ + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scim_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "scim_group_member_scim_user_idx": { + "name": "scim_group_member_scim_user_idx", + "columns": [ + { + "expression": "scim_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "scim_group_member_group_id_scim_group_id_fk": { + "name": "scim_group_member_group_id_scim_group_id_fk", + "tableFrom": "scim_group_member", + "columnsFrom": ["group_id"], + "tableTo": "scim_group", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "scim_group_member_scim_user_id_scim_user_id_fk": { + "name": "scim_group_member_scim_user_id_scim_user_id_fk", + "tableFrom": "scim_group_member", + "columnsFrom": ["scim_user_id"], + "tableTo": "scim_user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_projection_grant": { + "name": "scim_projection_grant", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scim_user_id": { + "name": "scim_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_kind": { + "name": "target_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission_type": { + "name": "permission_type", + "type": "permission_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "baseline_permission": { + "name": "baseline_permission", + "type": "permission_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "origin": { + "name": "origin", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'directory'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "scim_projection_grant_user_target_unique": { + "name": "scim_projection_grant_user_target_unique", + "columns": [ + { + "expression": "scim_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "scim_projection_grant_connection_idx": { + "name": "scim_projection_grant_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "scim_projection_grant_connection_id_scim_connection_id_fk": { + "name": "scim_projection_grant_connection_id_scim_connection_id_fk", + "tableFrom": "scim_projection_grant", + "columnsFrom": ["connection_id"], + "tableTo": "scim_connection", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "scim_projection_grant_scim_user_id_scim_user_id_fk": { + "name": "scim_projection_grant_scim_user_id_scim_user_id_fk", + "tableFrom": "scim_projection_grant", + "columnsFrom": ["scim_user_id"], + "tableTo": "scim_user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_request_log": { + "name": "scim_request_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "method": { + "name": "method", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "scim_type": { + "name": "scim_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "detail": { + "name": "detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "scim_request_log_connection_created_idx": { + "name": "scim_request_log_connection_created_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "scim_request_log_connection_id_scim_connection_id_fk": { + "name": "scim_request_log_connection_id_scim_connection_id_fk", + "tableFrom": "scim_request_log", + "columnsFrom": ["connection_id"], + "tableTo": "scim_connection", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_user": { + "name": "scim_user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_name": { + "name": "user_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "attributes": { + "name": "attributes", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "order_key": { + "name": "order_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "scim_user_connection_user_unique": { + "name": "scim_user_connection_user_unique", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "scim_user_connection_user_name_unique": { + "name": "scim_user_connection_user_name_unique", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "scim_user_connection_external_id_unique": { + "name": "scim_user_connection_external_id_unique", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "external_id is not null", + "concurrently": false + }, + "scim_user_connection_order_idx": { + "name": "scim_user_connection_order_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "order_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "scim_user_user_idx": { + "name": "scim_user_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "scim_user_connection_id_scim_connection_id_fk": { + "name": "scim_user_connection_id_scim_connection_id_fk", + "tableFrom": "scim_user", + "columnsFrom": ["connection_id"], + "tableTo": "scim_connection", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "scim_user_user_id_user_id_fk": { + "name": "scim_user_user_id_user_id_fk", + "tableFrom": "scim_user", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_user_tombstone": { + "name": "scim_user_tombstone", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "scim_user_tombstone_connection_external_id_unique": { + "name": "scim_user_tombstone_connection_external_id_unique", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "scim_user_tombstone_user_idx": { + "name": "scim_user_tombstone_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "scim_user_tombstone_connection_id_scim_connection_id_fk": { + "name": "scim_user_tombstone_connection_id_scim_connection_id_fk", + "tableFrom": "scim_user_tombstone", + "columnsFrom": ["connection_id"], + "tableTo": "scim_connection", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "scim_user_tombstone_user_id_user_id_fk": { + "name": "scim_user_tombstone_user_id_user_id_fk", + "tableFrom": "scim_user_tombstone", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.secret_usage": { + "name": "secret_usage", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret_name": { + "name": "secret_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret_scope": { + "name": "secret_scope", + "type": "secret_usage_scope", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "secret_owner_user_id": { + "name": "secret_owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "source": { + "name": "source", + "type": "secret_usage_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "usage_date": { + "name": "usage_date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "use_count": { + "name": "use_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "last_execution_id": { + "name": "last_execution_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_trigger": { + "name": "last_trigger", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "secret_usage_bucket_unique": { + "name": "secret_usage_bucket_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "secret_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "secret_scope", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "secret_owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "actor_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "usage_date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "secret_usage_secret_recent_idx": { + "name": "secret_usage_secret_recent_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "secret_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "secret_scope", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "secret_owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_used_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "secret_usage_workspace_id_workspace_id_fk": { + "name": "secret_usage_workspace_id_workspace_id_fk", + "tableFrom": "secret_usage", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "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 + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "session_user_id_idx": { + "name": "session_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "session_active_organization_id_organization_id_fk": { + "name": "session_active_organization_id_organization_id_fk", + "tableFrom": "session", + "columnsFrom": ["active_organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "columns": ["token"], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.settings": { + "name": "settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "theme": { + "name": "theme", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "auto_connect": { + "name": "auto_connect", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "telemetry_enabled": { + "name": "telemetry_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "email_preferences": { + "name": "email_preferences", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "billing_usage_notifications_enabled": { + "name": "billing_usage_notifications_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "show_training_controls": { + "name": "show_training_controls", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "super_user_mode_enabled": { + "name": "super_user_mode_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "mothership_environment": { + "name": "mothership_environment", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "error_notifications_enabled": { + "name": "error_notifications_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "snap_to_grid_size": { + "name": "snap_to_grid_size", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "show_action_bar": { + "name": "show_action_bar", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "auto_focus_on_click": { + "name": "auto_focus_on_click", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "copilot_enabled_models": { + "name": "copilot_enabled_models", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "copilot_auto_allowed_tools": { + "name": "copilot_auto_allowed_tools", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "last_active_workspace_id": { + "name": "last_active_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "settings_user_id_user_id_fk": { + "name": "settings_user_id_user_id_fk", + "tableFrom": "settings", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "settings_user_id_unique": { + "name": "settings_user_id_unique", + "columns": ["user_id"], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sim_trigger_state": { + "name": "sim_trigger_state", + "schema": "", + "columns": { + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "block_id": { + "name": "block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_key": { + "name": "scope_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "last_fired_at": { + "name": "last_fired_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "sim_trigger_state_workflow_id_workflow_id_fk": { + "name": "sim_trigger_state_workflow_id_workflow_id_fk", + "tableFrom": "sim_trigger_state", + "columnsFrom": ["workflow_id"], + "tableTo": "workflow", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "sim_trigger_state_workflow_id_block_id_scope_key_pk": { + "name": "sim_trigger_state_workflow_id_block_id_scope_key_pk", + "columns": ["workflow_id", "block_id", "scope_key"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.skill": { + "name": "skill", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "skill_workspace_name_unique": { + "name": "skill_workspace_name_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "skill_workspace_id_workspace_id_fk": { + "name": "skill_workspace_id_workspace_id_fk", + "tableFrom": "skill", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "skill_user_id_user_id_fk": { + "name": "skill_user_id_user_id_fk", + "tableFrom": "skill", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.skill_member": { + "name": "skill_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "skill_id": { + "name": "skill_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "invited_by": { + "name": "invited_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "skill_member_user_id_idx": { + "name": "skill_member_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "skill_member_unique": { + "name": "skill_member_unique", + "columns": [ + { + "expression": "skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "skill_member_skill_id_skill_id_fk": { + "name": "skill_member_skill_id_skill_id_fk", + "tableFrom": "skill_member", + "columnsFrom": ["skill_id"], + "tableTo": "skill", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "skill_member_user_id_user_id_fk": { + "name": "skill_member_user_id_user_id_fk", + "tableFrom": "skill_member", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "skill_member_invited_by_user_id_fk": { + "name": "skill_member_invited_by_user_id_fk", + "tableFrom": "skill_member", + "columnsFrom": ["invited_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.slack_app": { + "name": "slack_app", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_client_secret": { + "name": "encrypted_client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_signing_secret": { + "name": "encrypted_signing_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revision": { + "name": "revision", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "slack_app_organization_id_organization_id_fk": { + "name": "slack_app_organization_id_organization_id_fk", + "tableFrom": "slack_app", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "slack_app_owner_check": { + "name": "slack_app_owner_check", + "value": "(\"slack_app\".\"kind\" = 'custom' AND \"slack_app\".\"organization_id\" IS NOT NULL) OR (\"slack_app\".\"kind\" = 'shared' AND \"slack_app\".\"organization_id\" IS NULL)" + }, + "slack_app_custom_credentials_check": { + "name": "slack_app_custom_credentials_check", + "value": "\"slack_app\".\"kind\" = 'shared' OR (\"slack_app\".\"client_id\" IS NOT NULL AND \"slack_app\".\"encrypted_client_secret\" IS NOT NULL AND \"slack_app\".\"encrypted_signing_secret\" IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.slack_search_installation": { + "name": "slack_search_installation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "app_id": { + "name": "app_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slack_app_id": { + "name": "slack_app_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "team_name": { + "name": "team_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bot_user_id": { + "name": "bot_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enterprise_id": { + "name": "enterprise_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "credential_version": { + "name": "credential_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_outcome": { + "name": "last_outcome", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_event_at": { + "name": "last_event_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "slack_search_installation_organization_idx": { + "name": "slack_search_installation_organization_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "slack_search_installation_credential_unique": { + "name": "slack_search_installation_credential_unique", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "slack_search_installation_app_team_unique": { + "name": "slack_search_installation_app_team_unique", + "columns": [ + { + "expression": "app_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "slack_search_installation_active_team_unique": { + "name": "slack_search_installation_active_team_unique", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"slack_search_installation\".\"enabled\" = true", + "concurrently": false + } + }, + "foreignKeys": { + "slack_search_installation_organization_id_organization_id_fk": { + "name": "slack_search_installation_organization_id_organization_id_fk", + "tableFrom": "slack_search_installation", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "slack_search_installation_credential_id_credential_id_fk": { + "name": "slack_search_installation_credential_id_credential_id_fk", + "tableFrom": "slack_search_installation", + "columnsFrom": ["credential_id"], + "tableTo": "credential", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "slack_search_installation_slack_app_id_slack_app_id_fk": { + "name": "slack_search_installation_slack_app_id_slack_app_id_fk", + "tableFrom": "slack_search_installation", + "columnsFrom": ["slack_app_id"], + "tableTo": "slack_app", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.slack_search_turn": { + "name": "slack_search_turn", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "ordinal": { + "name": "ordinal", + "type": "integer", + "primaryKey": false, + "notNull": true, + "identity": { + "name": "slack_search_turn_ordinal_seq", + "increment": "1", + "minValue": "1", + "maxValue": "2147483647", + "startWith": "1", + "cache": "1", + "cycle": false, + "schema": "public", + "type": "always" + } + }, + "installation_id": { + "name": "installation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "conversation_key": { + "name": "conversation_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "lease_id": { + "name": "lease_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lease_expires_at": { + "name": "lease_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "slack_search_turn_event_unique": { + "name": "slack_search_turn_event_unique", + "columns": [ + { + "expression": "installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "slack_search_turn_pending_idx": { + "name": "slack_search_turn_pending_idx", + "columns": [ + { + "expression": "installation_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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "slack_search_turn_thread_idx": { + "name": "slack_search_turn_thread_idx", + "columns": [ + { + "expression": "conversation_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "slack_search_turn_active_thread_unique": { + "name": "slack_search_turn_active_thread_unique", + "columns": [ + { + "expression": "conversation_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"slack_search_turn\".\"status\" = 'running'", + "concurrently": false + } + }, + "foreignKeys": { + "slack_search_turn_installation_id_slack_search_installation_id_fk": { + "name": "slack_search_turn_installation_id_slack_search_installation_id_fk", + "tableFrom": "slack_search_turn", + "columnsFrom": ["installation_id"], + "tableTo": "slack_search_installation", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sso_domain": { + "name": "sso_domain", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "verification_token": { + "name": "verification_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "verified_at": { + "name": "verified_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "primary_provider_id": { + "name": "primary_provider_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "sso_domain_organization_id_idx": { + "name": "sso_domain_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "sso_domain_domain_idx": { + "name": "sso_domain_domain_idx", + "columns": [ + { + "expression": "domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "sso_domain_org_domain_unique": { + "name": "sso_domain_org_domain_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "sso_domain_verified_unique": { + "name": "sso_domain_verified_unique", + "columns": [ + { + "expression": "domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "status = 'verified'", + "concurrently": false + } + }, + "foreignKeys": { + "sso_domain_organization_id_organization_id_fk": { + "name": "sso_domain_organization_id_organization_id_fk", + "tableFrom": "sso_domain", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "sso_domain_created_by_user_id_fk": { + "name": "sso_domain_created_by_user_id_fk", + "tableFrom": "sso_domain", + "columnsFrom": ["created_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sso_provider": { + "name": "sso_provider", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "issuer": { + "name": "issuer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oidc_config": { + "name": "oidc_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "saml_config": { + "name": "saml_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "domain_verified": { + "name": "domain_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "jit_provisioning_enabled": { + "name": "jit_provisioning_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + } + }, + "indexes": { + "sso_provider_provider_id_unique": { + "name": "sso_provider_provider_id_unique", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "sso_provider_domain_idx": { + "name": "sso_provider_domain_idx", + "columns": [ + { + "expression": "domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "sso_provider_user_id_idx": { + "name": "sso_provider_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "sso_provider_organization_id_idx": { + "name": "sso_provider_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "sso_provider_user_id_user_id_fk": { + "name": "sso_provider_user_id_user_id_fk", + "tableFrom": "sso_provider", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "sso_provider_organization_id_organization_id_fk": { + "name": "sso_provider_organization_id_organization_id_fk", + "tableFrom": "sso_provider", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.subscription": { + "name": "subscription", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "plan": { + "name": "plan", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_subscription_id": { + "name": "stripe_subscription_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "period_start": { + "name": "period_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "period_end": { + "name": "period_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "cancel_at_period_end": { + "name": "cancel_at_period_end", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "cancel_at": { + "name": "cancel_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "canceled_at": { + "name": "canceled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "seats": { + "name": "seats", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "trial_start": { + "name": "trial_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "trial_end": { + "name": "trial_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "billing_interval": { + "name": "billing_interval", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_schedule_id": { + "name": "stripe_schedule_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "last_closed_period_start": { + "name": "last_closed_period_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "subscription_reference_status_idx": { + "name": "subscription_reference_status_idx", + "columns": [ + { + "expression": "reference_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "subscription_cycle_close_lagging_idx": { + "name": "subscription_cycle_close_lagging_idx", + "columns": [ + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"subscription\".\"status\" in ('active', 'past_due') and \"subscription\".\"period_start\" is not null and (\"subscription\".\"last_closed_period_start\" is null or \"subscription\".\"last_closed_period_start\" < \"subscription\".\"period_start\")", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "check_enterprise_metadata": { + "name": "check_enterprise_metadata", + "value": "plan != 'enterprise' OR metadata IS NOT NULL" + } + }, + "isRLSEnabled": false + }, + "public.table_jobs": { + "name": "table_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "rows_processed": { + "name": "rows_processed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "table_jobs_one_active_per_table": { + "name": "table_jobs_one_active_per_table", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"table_jobs\".\"status\" = 'running' AND \"table_jobs\".\"type\" <> 'export'", + "concurrently": false + }, + "table_jobs_watchdog_idx": { + "name": "table_jobs_watchdog_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "table_jobs_table_started_idx": { + "name": "table_jobs_table_started_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "table_jobs_table_id_user_table_definitions_id_fk": { + "name": "table_jobs_table_id_user_table_definitions_id_fk", + "tableFrom": "table_jobs", + "columnsFrom": ["table_id"], + "tableTo": "user_table_definitions", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "table_jobs_workspace_id_workspace_id_fk": { + "name": "table_jobs_workspace_id_workspace_id_fk", + "tableFrom": "table_jobs", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.table_row_executions": { + "name": "table_row_executions", + "schema": "", + "columns": { + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "row_id": { + "name": "row_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "job_id": { + "name": "job_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "running_block_ids": { + "name": "running_block_ids", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "block_errors": { + "name": "block_errors", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "capability_governed_user_id": { + "name": "capability_governed_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enrichment_details": { + "name": "enrichment_details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "table_row_executions_table_status_idx": { + "name": "table_row_executions_table_status_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"table_row_executions\".\"status\" IN ('queued', 'running', 'pending')", + "concurrently": false + }, + "table_row_executions_execution_id_idx": { + "name": "table_row_executions_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"table_row_executions\".\"execution_id\" IS NOT NULL", + "concurrently": false + }, + "table_row_executions_table_group_idx": { + "name": "table_row_executions_table_group_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "table_row_executions_table_id_user_table_definitions_id_fk": { + "name": "table_row_executions_table_id_user_table_definitions_id_fk", + "tableFrom": "table_row_executions", + "columnsFrom": ["table_id"], + "tableTo": "user_table_definitions", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "table_row_executions_row_id_user_table_rows_id_fk": { + "name": "table_row_executions_row_id_user_table_rows_id_fk", + "tableFrom": "table_row_executions", + "columnsFrom": ["row_id"], + "tableTo": "user_table_rows", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "table_row_executions_capability_governed_user_id_user_id_fk": { + "name": "table_row_executions_capability_governed_user_id_user_id_fk", + "tableFrom": "table_row_executions", + "columnsFrom": ["capability_governed_user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": { + "table_row_executions_row_id_group_id_pk": { + "name": "table_row_executions_row_id_group_id_pk", + "columns": ["row_id", "group_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.table_run_dispatches": { + "name": "table_run_dispatches", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "cursor": { + "name": "cursor", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "limit": { + "name": "limit", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "processed_count": { + "name": "processed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_manual_run": { + "name": "is_manual_run", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "triggered_by_user_id": { + "name": "triggered_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "capability_governed_user_id": { + "name": "capability_governed_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_at": { + "name": "requested_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "heartbeat_at": { + "name": "heartbeat_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "table_run_dispatches_active_idx": { + "name": "table_run_dispatches_active_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "table_run_dispatches_watchdog_idx": { + "name": "table_run_dispatches_watchdog_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "table_run_dispatches_governed_active_idx": { + "name": "table_run_dispatches_governed_active_idx", + "columns": [ + { + "expression": "capability_governed_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"table_run_dispatches\".\"status\" IN ('pending', 'dispatching')", + "concurrently": false + } + }, + "foreignKeys": { + "table_run_dispatches_table_id_user_table_definitions_id_fk": { + "name": "table_run_dispatches_table_id_user_table_definitions_id_fk", + "tableFrom": "table_run_dispatches", + "columnsFrom": ["table_id"], + "tableTo": "user_table_definitions", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "table_run_dispatches_workspace_id_workspace_id_fk": { + "name": "table_run_dispatches_workspace_id_workspace_id_fk", + "tableFrom": "table_run_dispatches", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "table_run_dispatches_triggered_by_user_id_user_id_fk": { + "name": "table_run_dispatches_triggered_by_user_id_user_id_fk", + "tableFrom": "table_run_dispatches", + "columnsFrom": ["triggered_by_user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + }, + "table_run_dispatches_capability_governed_user_id_user_id_fk": { + "name": "table_run_dispatches_capability_governed_user_id_user_id_fk", + "tableFrom": "table_run_dispatches", + "columnsFrom": ["capability_governed_user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.table_views": { + "name": "table_views", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "table_views_table_created_idx": { + "name": "table_views_table_created_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "table_views_workspace_created_idx": { + "name": "table_views_workspace_created_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "table_views_table_default_unique": { + "name": "table_views_table_default_unique", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "is_default = true", + "concurrently": false + } + }, + "foreignKeys": { + "table_views_table_id_user_table_definitions_id_fk": { + "name": "table_views_table_id_user_table_definitions_id_fk", + "tableFrom": "table_views", + "columnsFrom": ["table_id"], + "tableTo": "user_table_definitions", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "table_views_workspace_id_workspace_id_fk": { + "name": "table_views_workspace_id_workspace_id_fk", + "tableFrom": "table_views", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "table_views_created_by_user_id_fk": { + "name": "table_views_created_by_user_id_fk", + "tableFrom": "table_views", + "columnsFrom": ["created_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.upload_session": { + "name": "upload_session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "purpose": { + "name": "purpose", + "type": "upload_session_purpose", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "method": { + "name": "method", + "type": "upload_session_method", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "storage_context": { + "name": "storage_context", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "final_key": { + "name": "final_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "storage_provider": { + "name": "storage_provider", + "type": "upload_session_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "provider_upload_id": { + "name": "provider_upload_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_object_version": { + "name": "provider_object_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "file_name": { + "name": "file_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_size": { + "name": "file_size", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "part_size": { + "name": "part_size", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "part_count": { + "name": "part_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "upload_session_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'uploading'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "processing_lease_id": { + "name": "processing_lease_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "processing_lease_expires_at": { + "name": "processing_lease_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_file_id": { + "name": "completed_file_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "upload_session_token_hash_unique": { + "name": "upload_session_token_hash_unique", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "upload_session_final_key_unique": { + "name": "upload_session_final_key_unique", + "columns": [ + { + "expression": "final_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "upload_session_status_expires_at_idx": { + "name": "upload_session_status_expires_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.usage_log": { + "name": "usage_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "category": { + "name": "category", + "type": "usage_log_category", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "usage_log_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "cost": { + "name": "cost", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "event_key": { + "name": "event_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "billing_entity_type": { + "name": "billing_entity_type", + "type": "billing_entity_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "billing_entity_id": { + "name": "billing_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "billing_period_start": { + "name": "billing_period_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "billing_period_end": { + "name": "billing_period_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "usage_log_user_created_at_idx": { + "name": "usage_log_user_created_at_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "usage_log_source_idx": { + "name": "usage_log_source_idx", + "columns": [ + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "usage_log_workspace_id_idx": { + "name": "usage_log_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "usage_log_workflow_id_idx": { + "name": "usage_log_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "usage_log_event_key_unique": { + "name": "usage_log_event_key_unique", + "columns": [ + { + "expression": "event_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"usage_log\".\"event_key\" IS NOT NULL", + "concurrently": false + }, + "usage_log_billing_entity_period_idx": { + "name": "usage_log_billing_entity_period_idx", + "columns": [ + { + "expression": "billing_entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_start", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_end", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"usage_log\".\"billing_entity_type\" IS NOT NULL", + "concurrently": false + }, + "usage_log_billing_period_cost_idx": { + "name": "usage_log_billing_period_cost_idx", + "columns": [ + { + "expression": "billing_entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_start", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_end", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"usage_log\".\"billing_entity_type\" IS NOT NULL", + "concurrently": false + }, + "usage_log_billing_entity_created_at_cost_idx": { + "name": "usage_log_billing_entity_created_at_cost_idx", + "columns": [ + { + "expression": "billing_entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"usage_log\".\"billing_entity_type\" IS NOT NULL", + "concurrently": false + }, + "usage_log_workspace_created_at_idx": { + "name": "usage_log_workspace_created_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "usage_log_execution_id_idx": { + "name": "usage_log_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "usage_log_user_id_user_id_fk": { + "name": "usage_log_user_id_user_id_fk", + "tableFrom": "usage_log", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "usage_log_workspace_id_workspace_id_fk": { + "name": "usage_log_workspace_id_workspace_id_fk", + "tableFrom": "usage_log", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + }, + "usage_log_workflow_id_workflow_id_fk": { + "name": "usage_log_workflow_id_workflow_id_fk", + "tableFrom": "usage_log", + "columnsFrom": ["workflow_id"], + "tableTo": "workflow", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "usage_log_billing_scope_all_or_none": { + "name": "usage_log_billing_scope_all_or_none", + "value": "(\n (\"usage_log\".\"billing_entity_type\" IS NULL AND \"usage_log\".\"billing_entity_id\" IS NULL AND \"usage_log\".\"billing_period_start\" IS NULL AND \"usage_log\".\"billing_period_end\" IS NULL)\n OR\n (\"usage_log\".\"billing_entity_type\" IS NOT NULL AND \"usage_log\".\"billing_entity_id\" IS NOT NULL AND \"usage_log\".\"billing_period_start\" IS NOT NULL AND \"usage_log\".\"billing_period_end\" IS NOT NULL AND \"usage_log\".\"billing_period_start\" < \"usage_log\".\"billing_period_end\")\n )" + } + }, + "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 + }, + "normalized_email": { + "name": "normalized_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "suspended_at": { + "name": "suspended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "suspension_source": { + "name": "suspension_source", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_email_lower_idx": { + "name": "user_email_lower_idx", + "columns": [ + { + "expression": "lower(btrim(\"email\"))", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "columns": ["email"], + "nullsNotDistinct": false + }, + "user_normalized_email_unique": { + "name": "user_normalized_email_unique", + "columns": ["normalized_email"], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_stats": { + "name": "user_stats", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "current_usage_limit": { + "name": "current_usage_limit", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'5'" + }, + "usage_limit_updated_at": { + "name": "usage_limit_updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "last_period_cost": { + "name": "last_period_cost", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "billed_overage_this_period": { + "name": "billed_overage_this_period", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "credit_balance": { + "name": "credit_balance", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "last_period_copilot_cost": { + "name": "last_period_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "storage_used_bytes": { + "name": "storage_used_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "billing_blocked": { + "name": "billing_blocked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "billing_blocked_reason": { + "name": "billing_blocked_reason", + "type": "billing_blocked_reason", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "limit_notifications": { + "name": "limit_notifications", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + } + }, + "indexes": {}, + "foreignKeys": { + "user_stats_user_id_user_id_fk": { + "name": "user_stats_user_id_user_id_fk", + "tableFrom": "user_stats", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_stats_user_id_unique": { + "name": "user_stats_user_id_unique", + "columns": ["user_id"], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_table_definitions": { + "name": "user_table_definitions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "folder_id": { + "name": "folder_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "schema": { + "name": "schema", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "max_rows": { + "name": "max_rows", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10000 + }, + "row_count": { + "name": "row_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "rows_version": { + "name": "rows_version", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "schema_locked": { + "name": "schema_locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "insert_locked": { + "name": "insert_locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "update_locked": { + "name": "update_locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "delete_locked": { + "name": "delete_locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_table_def_workspace_id_idx": { + "name": "user_table_def_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "user_table_def_folder_id_idx": { + "name": "user_table_def_folder_id_idx", + "columns": [ + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "user_table_def_workspace_name_unique": { + "name": "user_table_def_workspace_name_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"user_table_definitions\".\"archived_at\" IS NULL", + "concurrently": false + }, + "user_table_def_archived_at_idx": { + "name": "user_table_def_archived_at_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "user_table_def_workspace_archived_partial_idx": { + "name": "user_table_def_workspace_archived_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"user_table_definitions\".\"archived_at\" IS NOT NULL", + "concurrently": false + } + }, + "foreignKeys": { + "user_table_definitions_workspace_id_workspace_id_fk": { + "name": "user_table_definitions_workspace_id_workspace_id_fk", + "tableFrom": "user_table_definitions", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "user_table_definitions_folder_id_folder_id_fk": { + "name": "user_table_definitions_folder_id_folder_id_fk", + "tableFrom": "user_table_definitions", + "columnsFrom": ["folder_id"], + "tableTo": "folder", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + }, + "user_table_definitions_created_by_user_id_fk": { + "name": "user_table_definitions_created_by_user_id_fk", + "tableFrom": "user_table_definitions", + "columnsFrom": ["created_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_table_row_secret_provenance": { + "name": "user_table_row_secret_provenance", + "schema": "", + "columns": { + "row_id": { + "name": "row_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "content_updated_at": { + "name": "content_updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entries": { + "name": "entries", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_table_row_secret_provenance_row_id_user_table_rows_id_fk": { + "name": "user_table_row_secret_provenance_row_id_user_table_rows_id_fk", + "tableFrom": "user_table_row_secret_provenance", + "columnsFrom": ["row_id"], + "tableTo": "user_table_rows", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "user_table_row_secret_provenance_status_check": { + "name": "user_table_row_secret_provenance_status_check", + "value": "\"user_table_row_secret_provenance\".\"status\" IN ('exact', 'unknown')" + } + }, + "isRLSEnabled": false + }, + "public.user_table_rows": { + "name": "user_table_rows", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "order_key": { + "name": "order_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_provenance_version": { + "name": "secret_provenance_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_table_rows_tenant_data_gin_idx": { + "name": "user_table_rows_tenant_data_gin_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"data\" jsonb_path_ops", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "gin", + "concurrently": false + }, + "user_table_rows_workspace_table_idx": { + "name": "user_table_rows_workspace_table_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "user_table_rows_table_position_idx": { + "name": "user_table_rows_table_position_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "user_table_rows_table_order_key_idx": { + "name": "user_table_rows_table_order_key_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "order_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "user_table_rows_table_created_id_idx": { + "name": "user_table_rows_table_created_id_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "user_table_rows_table_id_id_idx": { + "name": "user_table_rows_table_id_id_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "user_table_rows_table_id_user_table_definitions_id_fk": { + "name": "user_table_rows_table_id_user_table_definitions_id_fk", + "tableFrom": "user_table_rows", + "columnsFrom": ["table_id"], + "tableTo": "user_table_definitions", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "user_table_rows_workspace_id_workspace_id_fk": { + "name": "user_table_rows_workspace_id_workspace_id_fk", + "tableFrom": "user_table_rows", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "user_table_rows_created_by_user_id_fk": { + "name": "user_table_rows_created_by_user_id_fk", + "tableFrom": "user_table_rows", + "columnsFrom": ["created_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "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", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "verification_identifier_idx": { + "name": "verification_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "verification_expires_at_idx": { + "name": "verification_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.waitlist": { + "name": "waitlist", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "waitlist_email_unique": { + "name": "waitlist_email_unique", + "columns": ["email"], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.webhook": { + "name": "webhook", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deployment_version_id": { + "name": "deployment_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "registration_status": { + "name": "registration_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "registration_generation": { + "name": "registration_generation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "config_fingerprint": { + "name": "config_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prepared_at": { + "name": "prepared_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "block_id": { + "name": "block_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "routing_key": { + "name": "routing_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_config": { + "name": "provider_config", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "failed_count": { + "name": "failed_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "last_failed_at": { + "name": "last_failed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "path_deployment_unique": { + "name": "path_deployment_unique", + "columns": [ + { + "expression": "path", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"webhook\".\"archived_at\" IS NULL", + "concurrently": false + }, + "webhook_workflow_deployment_idx": { + "name": "webhook_workflow_deployment_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "webhook_routing_key_active_idx": { + "name": "webhook_routing_key_active_idx", + "columns": [ + { + "expression": "routing_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"webhook\".\"archived_at\" IS NULL AND \"webhook\".\"routing_key\" IS NOT NULL", + "concurrently": false + }, + "webhook_archived_at_partial_idx": { + "name": "webhook_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"webhook\".\"archived_at\" IS NOT NULL", + "concurrently": false + }, + "idx_webhook_on_provider_is_active_workflow_id_deploym_bdeed5468": { + "name": "idx_webhook_on_provider_is_active_workflow_id_deploym_bdeed5468", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_webhook_on_workflow_id_block_id_updated_at_desc": { + "name": "idx_webhook_on_workflow_id_block_id_updated_at_desc", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "webhook_active_registration_unique": { + "name": "webhook_active_registration_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"webhook\".\"registration_status\" = 'active' AND \"webhook\".\"block_id\" IS NOT NULL AND \"webhook\".\"archived_at\" IS NULL", + "concurrently": false + }, + "webhook_candidate_registration_unique": { + "name": "webhook_candidate_registration_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"webhook\".\"registration_status\" = 'candidate' AND \"webhook\".\"block_id\" IS NOT NULL", + "concurrently": false + }, + "webhook_registration_status_generation_idx": { + "name": "webhook_registration_status_generation_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "registration_status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "registration_generation", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "webhook_workflow_id_workflow_id_fk": { + "name": "webhook_workflow_id_workflow_id_fk", + "tableFrom": "webhook", + "columnsFrom": ["workflow_id"], + "tableTo": "workflow", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "webhook_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "webhook_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "webhook", + "columnsFrom": ["deployment_version_id"], + "tableTo": "workflow_deployment_version", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "webhook_registration_status_check": { + "name": "webhook_registration_status_check", + "value": "\"webhook\".\"registration_status\" IS NULL OR \"webhook\".\"registration_status\" IN ('active', 'candidate', 'retired', 'orphaned')" + }, + "webhook_registration_generation_check": { + "name": "webhook_registration_generation_check", + "value": "\"webhook\".\"registration_generation\" IS NULL OR \"webhook\".\"registration_generation\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.webhook_path_claim": { + "name": "webhook_path_claim", + "schema": "", + "columns": { + "path": { + "name": "path", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "webhook_path_claim_workflow_idx": { + "name": "webhook_path_claim_workflow_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "webhook_path_claim_workflow_id_workflow_id_fk": { + "name": "webhook_path_claim_workflow_id_workflow_id_fk", + "tableFrom": "webhook_path_claim", + "columnsFrom": ["workflow_id"], + "tableTo": "workflow", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "webhook_path_claim_generation_check": { + "name": "webhook_path_claim_generation_check", + "value": "\"webhook_path_claim\".\"generation\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.workflow": { + "name": "workflow", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "folder_id": { + "name": "folder_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_synced": { + "name": "last_synced", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "is_deployed": { + "name": "is_deployed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "deployed_at": { + "name": "deployed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "is_public_api": { + "name": "is_public_api", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "locked": { + "name": "locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "fork_sync_excluded": { + "name": "fork_sync_excluded", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "run_count": { + "name": "run_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "variables": { + "name": "variables", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workflow_user_id_idx": { + "name": "workflow_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_workspace_id_idx": { + "name": "workflow_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_user_workspace_idx": { + "name": "workflow_user_workspace_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_workspace_folder_name_active_unique": { + "name": "workflow_workspace_folder_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"folder_id\", '')", + "isExpression": true, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"workflow\".\"archived_at\" IS NULL", + "concurrently": false + }, + "workflow_folder_sort_idx": { + "name": "workflow_folder_sort_idx", + "columns": [ + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_active_workspace_sort_idx": { + "name": "workflow_active_workspace_sort_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"workflow\".\"archived_at\" IS NULL", + "concurrently": false + }, + "workflow_archived_at_idx": { + "name": "workflow_archived_at_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_workspace_archived_partial_idx": { + "name": "workflow_workspace_archived_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"workflow\".\"archived_at\" IS NOT NULL", + "concurrently": false + } + }, + "foreignKeys": { + "workflow_user_id_user_id_fk": { + "name": "workflow_user_id_user_id_fk", + "tableFrom": "workflow", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workflow_workspace_id_workspace_id_fk": { + "name": "workflow_workspace_id_workspace_id_fk", + "tableFrom": "workflow", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workflow_folder_id_folder_id_fk": { + "name": "workflow_folder_id_folder_id_fk", + "tableFrom": "workflow", + "columnsFrom": ["folder_id"], + "tableTo": "folder", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_blocks": { + "name": "workflow_blocks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "position_x": { + "name": "position_x", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "position_y": { + "name": "position_y", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "horizontal_handles": { + "name": "horizontal_handles", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_wide": { + "name": "is_wide", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "advanced_mode": { + "name": "advanced_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "trigger_mode": { + "name": "trigger_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "error_enabled": { + "name": "error_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "retry": { + "name": "retry", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "locked": { + "name": "locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "height": { + "name": "height", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "sub_blocks": { + "name": "sub_blocks", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "outputs": { + "name": "outputs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_blocks_workflow_id_idx": { + "name": "workflow_blocks_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_blocks_type_idx": { + "name": "workflow_blocks_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "workflow_blocks_workflow_id_workflow_id_fk": { + "name": "workflow_blocks_workflow_id_workflow_id_fk", + "tableFrom": "workflow_blocks", + "columnsFrom": ["workflow_id"], + "tableTo": "workflow", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_checkpoints": { + "name": "workflow_checkpoints", + "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 + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_state": { + "name": "workflow_state", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_checkpoints_user_id_idx": { + "name": "workflow_checkpoints_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_checkpoints_workflow_id_idx": { + "name": "workflow_checkpoints_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_checkpoints_chat_id_idx": { + "name": "workflow_checkpoints_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_checkpoints_message_id_idx": { + "name": "workflow_checkpoints_message_id_idx", + "columns": [ + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_checkpoints_user_workflow_idx": { + "name": "workflow_checkpoints_user_workflow_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_checkpoints_workflow_chat_idx": { + "name": "workflow_checkpoints_workflow_chat_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_checkpoints_created_at_idx": { + "name": "workflow_checkpoints_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_checkpoints_chat_created_at_idx": { + "name": "workflow_checkpoints_chat_created_at_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "workflow_checkpoints_user_id_user_id_fk": { + "name": "workflow_checkpoints_user_id_user_id_fk", + "tableFrom": "workflow_checkpoints", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workflow_checkpoints_workflow_id_workflow_id_fk": { + "name": "workflow_checkpoints_workflow_id_workflow_id_fk", + "tableFrom": "workflow_checkpoints", + "columnsFrom": ["workflow_id"], + "tableTo": "workflow", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workflow_checkpoints_chat_id_copilot_chats_id_fk": { + "name": "workflow_checkpoints_chat_id_copilot_chats_id_fk", + "tableFrom": "workflow_checkpoints", + "columnsFrom": ["chat_id"], + "tableTo": "copilot_chats", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_deployment_operation": { + "name": "workflow_deployment_operation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deployment_version_id": { + "name": "deployment_version_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "previous_active_version_id": { + "name": "previous_active_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "protocol_version": { + "name": "protocol_version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'preparing'" + }, + "component_readiness": { + "name": "component_readiness", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_hash": { + "name": "request_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_deployment_operation_workflow_generation_unique": { + "name": "workflow_deployment_operation_workflow_generation_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "generation", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_deployment_operation_workflow_idempotency_unique": { + "name": "workflow_deployment_operation_workflow_idempotency_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"workflow_deployment_operation\".\"idempotency_key\" IS NOT NULL", + "concurrently": false + }, + "workflow_deployment_operation_workflow_in_flight_unique": { + "name": "workflow_deployment_operation_workflow_in_flight_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"workflow_deployment_operation\".\"status\" IN ('preparing', 'activating')", + "concurrently": false + }, + "workflow_deployment_operation_workflow_status_idx": { + "name": "workflow_deployment_operation_workflow_status_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_deployment_operation_deployment_version_idx": { + "name": "workflow_deployment_operation_deployment_version_idx", + "columns": [ + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_deployment_operation_workflow_version_generation_idx": { + "name": "workflow_deployment_operation_workflow_version_generation_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "generation", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "workflow_deployment_operation_workflow_id_workflow_id_fk": { + "name": "workflow_deployment_operation_workflow_id_workflow_id_fk", + "tableFrom": "workflow_deployment_operation", + "columnsFrom": ["workflow_id"], + "tableTo": "workflow", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workflow_deployment_operation_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "workflow_deployment_operation_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "workflow_deployment_operation", + "columnsFrom": ["deployment_version_id"], + "tableTo": "workflow_deployment_version", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workflow_deployment_operation_previous_active_version_id_workflow_deployment_version_id_fk": { + "name": "workflow_deployment_operation_previous_active_version_id_workflow_deployment_version_id_fk", + "tableFrom": "workflow_deployment_operation", + "columnsFrom": ["previous_active_version_id"], + "tableTo": "workflow_deployment_version", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "workflow_deployment_operation_action_check": { + "name": "workflow_deployment_operation_action_check", + "value": "\"workflow_deployment_operation\".\"action\" IN ('deploy', 'activate')" + }, + "workflow_deployment_operation_status_check": { + "name": "workflow_deployment_operation_status_check", + "value": "\"workflow_deployment_operation\".\"status\" IN ('preparing', 'activating', 'active', 'failed', 'superseded')" + }, + "workflow_deployment_operation_generation_check": { + "name": "workflow_deployment_operation_generation_check", + "value": "\"workflow_deployment_operation\".\"generation\" > 0" + }, + "workflow_deployment_operation_protocol_version_check": { + "name": "workflow_deployment_operation_protocol_version_check", + "value": "\"workflow_deployment_operation\".\"protocol_version\" > 0" + } + }, + "isRLSEnabled": false + }, + "public.workflow_deployment_version": { + "name": "workflow_deployment_version", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workflow_deployment_version_workflow_version_unique": { + "name": "workflow_deployment_version_workflow_version_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_deployment_version_workflow_active_idx": { + "name": "workflow_deployment_version_workflow_active_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_deployment_version_created_at_idx": { + "name": "workflow_deployment_version_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "workflow_deployment_version_workflow_id_workflow_id_fk": { + "name": "workflow_deployment_version_workflow_id_workflow_id_fk", + "tableFrom": "workflow_deployment_version", + "columnsFrom": ["workflow_id"], + "tableTo": "workflow", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_edges": { + "name": "workflow_edges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_block_id": { + "name": "source_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_block_id": { + "name": "target_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_handle": { + "name": "source_handle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_handle": { + "name": "target_handle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_edges_workflow_id_idx": { + "name": "workflow_edges_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_edges_workflow_source_idx": { + "name": "workflow_edges_workflow_source_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_edges_workflow_target_idx": { + "name": "workflow_edges_workflow_target_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "workflow_edges_workflow_id_workflow_id_fk": { + "name": "workflow_edges_workflow_id_workflow_id_fk", + "tableFrom": "workflow_edges", + "columnsFrom": ["workflow_id"], + "tableTo": "workflow", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workflow_edges_source_block_id_workflow_blocks_id_fk": { + "name": "workflow_edges_source_block_id_workflow_blocks_id_fk", + "tableFrom": "workflow_edges", + "columnsFrom": ["source_block_id"], + "tableTo": "workflow_blocks", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workflow_edges_target_block_id_workflow_blocks_id_fk": { + "name": "workflow_edges_target_block_id_workflow_blocks_id_fk", + "tableFrom": "workflow_edges", + "columnsFrom": ["target_block_id"], + "tableTo": "workflow_blocks", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_execution_logs": { + "name": "workflow_execution_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state_snapshot_id": { + "name": "state_snapshot_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deployment_version_id": { + "name": "deployment_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "execution_deadline_at": { + "name": "execution_deadline_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "total_duration_ms": { + "name": "total_duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "execution_data": { + "name": "execution_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "cost_total": { + "name": "cost_total", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "models_used": { + "name": "models_used", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "files": { + "name": "files", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_execution_logs_workflow_id_idx": { + "name": "workflow_execution_logs_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_execution_logs_state_snapshot_id_idx": { + "name": "workflow_execution_logs_state_snapshot_id_idx", + "columns": [ + { + "expression": "state_snapshot_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_execution_logs_deployment_version_id_idx": { + "name": "workflow_execution_logs_deployment_version_id_idx", + "columns": [ + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_execution_logs_trigger_idx": { + "name": "workflow_execution_logs_trigger_idx", + "columns": [ + { + "expression": "trigger", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_execution_logs_level_idx": { + "name": "workflow_execution_logs_level_idx", + "columns": [ + { + "expression": "level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_execution_logs_started_at_idx": { + "name": "workflow_execution_logs_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_execution_logs_execution_id_unique": { + "name": "workflow_execution_logs_execution_id_unique", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_execution_logs_workflow_started_at_idx": { + "name": "workflow_execution_logs_workflow_started_at_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_execution_logs_workspace_started_at_idx": { + "name": "workflow_execution_logs_workspace_started_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_execution_logs_workspace_activity_idx": { + "name": "workflow_execution_logs_workspace_activity_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "total_duration_ms", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "trigger", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": true + }, + "workflow_execution_logs_workspace_started_at_id_desc_idx": { + "name": "workflow_execution_logs_workspace_started_at_id_desc_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"started_at\" DESC NULLS LAST", + "isExpression": true, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"id\" DESC", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_execution_logs_workspace_cost_total_idx": { + "name": "workflow_execution_logs_workspace_cost_total_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_total", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_execution_logs_models_used_idx": { + "name": "workflow_execution_logs_models_used_idx", + "columns": [ + { + "expression": "models_used", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "gin", + "concurrently": false + }, + "workflow_execution_logs_workspace_ended_at_id_idx": { + "name": "workflow_execution_logs_workspace_ended_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"ended_at\")", + "isExpression": true, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_execution_logs_running_started_at_idx": { + "name": "workflow_execution_logs_running_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "status = 'running'", + "concurrently": false + }, + "workflow_execution_logs_running_deadline_idx": { + "name": "workflow_execution_logs_running_deadline_idx", + "columns": [ + { + "expression": "execution_deadline_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"workflow_execution_logs\".\"status\" = 'running' AND \"workflow_execution_logs\".\"execution_deadline_at\" IS NOT NULL", + "concurrently": false + }, + "workflow_execution_logs_redacting_started_at_idx": { + "name": "workflow_execution_logs_redacting_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "status = 'redacting'", + "concurrently": false + }, + "workflow_execution_logs_redacting_deadline_idx": { + "name": "workflow_execution_logs_redacting_deadline_idx", + "columns": [ + { + "expression": "execution_deadline_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"workflow_execution_logs\".\"status\" = 'redacting' AND \"workflow_execution_logs\".\"execution_deadline_at\" IS NOT NULL", + "concurrently": false + }, + "workflow_execution_logs_completed_ended_at_idx": { + "name": "workflow_execution_logs_completed_ended_at_idx", + "columns": [ + { + "expression": "ended_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"workflow_execution_logs\".\"status\" = 'completed' AND \"workflow_execution_logs\".\"level\" = 'info' AND \"workflow_execution_logs\".\"ended_at\" IS NOT NULL", + "concurrently": false + } + }, + "foreignKeys": { + "workflow_execution_logs_workflow_id_workflow_id_fk": { + "name": "workflow_execution_logs_workflow_id_workflow_id_fk", + "tableFrom": "workflow_execution_logs", + "columnsFrom": ["workflow_id"], + "tableTo": "workflow", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + }, + "workflow_execution_logs_workspace_id_workspace_id_fk": { + "name": "workflow_execution_logs_workspace_id_workspace_id_fk", + "tableFrom": "workflow_execution_logs", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workflow_execution_logs_state_snapshot_id_workflow_execution_snapshots_id_fk": { + "name": "workflow_execution_logs_state_snapshot_id_workflow_execution_snapshots_id_fk", + "tableFrom": "workflow_execution_logs", + "columnsFrom": ["state_snapshot_id"], + "tableTo": "workflow_execution_snapshots", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "no action" + }, + "workflow_execution_logs_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "workflow_execution_logs_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "workflow_execution_logs", + "columnsFrom": ["deployment_version_id"], + "tableTo": "workflow_deployment_version", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_execution_snapshots": { + "name": "workflow_execution_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state_hash": { + "name": "state_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state_data": { + "name": "state_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_snapshots_workflow_id_idx": { + "name": "workflow_snapshots_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_snapshots_hash_idx": { + "name": "workflow_snapshots_hash_idx", + "columns": [ + { + "expression": "state_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_snapshots_workflow_hash_idx": { + "name": "workflow_snapshots_workflow_hash_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "state_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_snapshots_created_at_idx": { + "name": "workflow_snapshots_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "workflow_execution_snapshots_workflow_id_workflow_id_fk": { + "name": "workflow_execution_snapshots_workflow_id_workflow_id_fk", + "tableFrom": "workflow_execution_snapshots", + "columnsFrom": ["workflow_id"], + "tableTo": "workflow", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_mcp_server": { + "name": "workflow_mcp_server", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_mcp_server_workspace_id_idx": { + "name": "workflow_mcp_server_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_mcp_server_created_by_idx": { + "name": "workflow_mcp_server_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_mcp_server_deleted_at_idx": { + "name": "workflow_mcp_server_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_mcp_server_workspace_deleted_partial_idx": { + "name": "workflow_mcp_server_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"workflow_mcp_server\".\"deleted_at\" IS NOT NULL", + "concurrently": false + } + }, + "foreignKeys": { + "workflow_mcp_server_workspace_id_workspace_id_fk": { + "name": "workflow_mcp_server_workspace_id_workspace_id_fk", + "tableFrom": "workflow_mcp_server", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workflow_mcp_server_created_by_user_id_fk": { + "name": "workflow_mcp_server_created_by_user_id_fk", + "tableFrom": "workflow_mcp_server", + "columnsFrom": ["created_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_mcp_tool": { + "name": "workflow_mcp_tool", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "server_id": { + "name": "server_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_description": { + "name": "tool_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "parameter_schema": { + "name": "parameter_schema", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "parameter_description_overrides": { + "name": "parameter_description_overrides", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'::json" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_mcp_tool_server_id_idx": { + "name": "workflow_mcp_tool_server_id_idx", + "columns": [ + { + "expression": "server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_mcp_tool_workflow_id_idx": { + "name": "workflow_mcp_tool_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_mcp_tool_server_workflow_unique": { + "name": "workflow_mcp_tool_server_workflow_unique", + "columns": [ + { + "expression": "server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"workflow_mcp_tool\".\"archived_at\" IS NULL", + "concurrently": false + }, + "workflow_mcp_tool_archived_at_partial_idx": { + "name": "workflow_mcp_tool_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"workflow_mcp_tool\".\"archived_at\" IS NOT NULL", + "concurrently": false + } + }, + "foreignKeys": { + "workflow_mcp_tool_server_id_workflow_mcp_server_id_fk": { + "name": "workflow_mcp_tool_server_id_workflow_mcp_server_id_fk", + "tableFrom": "workflow_mcp_tool", + "columnsFrom": ["server_id"], + "tableTo": "workflow_mcp_server", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workflow_mcp_tool_workflow_id_workflow_id_fk": { + "name": "workflow_mcp_tool_workflow_id_workflow_id_fk", + "tableFrom": "workflow_mcp_tool", + "columnsFrom": ["workflow_id"], + "tableTo": "workflow", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_schedule": { + "name": "workflow_schedule", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deployment_version_id": { + "name": "deployment_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deployment_operation_id": { + "name": "deployment_operation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "block_id": { + "name": "block_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_ran_at": { + "name": "last_ran_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_queued_at": { + "name": "last_queued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "trigger_type": { + "name": "trigger_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "failed_count": { + "name": "failed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "infra_retry_count": { + "name": "infra_retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_failed_at": { + "name": "last_failed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'workflow'" + }, + "job_title": { + "name": "job_title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prompt": { + "name": "prompt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lifecycle": { + "name": "lifecycle", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'persistent'" + }, + "success_condition": { + "name": "success_condition", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "max_runs": { + "name": "max_runs", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "run_count": { + "name": "run_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "source_chat_id": { + "name": "source_chat_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_task_name": { + "name": "source_task_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_user_id": { + "name": "source_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_workspace_id": { + "name": "source_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_scope": { + "name": "secret_scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'all'" + }, + "mounted_secrets": { + "name": "mounted_secrets", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "job_history": { + "name": "job_history", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "contexts": { + "name": "contexts", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "excluded_dates": { + "name": "excluded_dates", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "ends_at": { + "name": "ends_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_schedule_workflow_block_deployment_unique": { + "name": "workflow_schedule_workflow_block_deployment_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"workflow_schedule\".\"archived_at\" IS NULL", + "concurrently": false + }, + "workflow_schedule_workflow_deployment_idx": { + "name": "workflow_schedule_workflow_deployment_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_schedule_archived_at_partial_idx": { + "name": "workflow_schedule_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"workflow_schedule\".\"archived_at\" IS NOT NULL", + "concurrently": false + }, + "idx_workflow_schedule_on_source_workspace_id_source_t_c07f3bba6": { + "name": "idx_workflow_schedule_on_source_workspace_id_source_t_c07f3bba6", + "columns": [ + { + "expression": "source_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_schedule_due_workflow_idx": { + "name": "workflow_schedule_due_workflow_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"workflow_schedule\".\"archived_at\" IS NULL AND \"workflow_schedule\".\"status\" NOT IN ('disabled', 'completed') AND (\"workflow_schedule\".\"source_type\" = 'workflow' OR \"workflow_schedule\".\"source_type\" IS NULL)", + "concurrently": false + }, + "workflow_schedule_due_job_idx": { + "name": "workflow_schedule_due_job_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"workflow_schedule\".\"archived_at\" IS NULL AND \"workflow_schedule\".\"status\" NOT IN ('disabled', 'completed') AND \"workflow_schedule\".\"source_type\" = 'job'", + "concurrently": false + } + }, + "foreignKeys": { + "workflow_schedule_workflow_id_workflow_id_fk": { + "name": "workflow_schedule_workflow_id_workflow_id_fk", + "tableFrom": "workflow_schedule", + "columnsFrom": ["workflow_id"], + "tableTo": "workflow", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workflow_schedule_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "workflow_schedule_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "workflow_schedule", + "columnsFrom": ["deployment_version_id"], + "tableTo": "workflow_deployment_version", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workflow_schedule_deployment_operation_id_workflow_deployment_operation_id_fk": { + "name": "workflow_schedule_deployment_operation_id_workflow_deployment_operation_id_fk", + "tableFrom": "workflow_schedule", + "columnsFrom": ["deployment_operation_id"], + "tableTo": "workflow_deployment_operation", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + }, + "workflow_schedule_source_user_id_user_id_fk": { + "name": "workflow_schedule_source_user_id_user_id_fk", + "tableFrom": "workflow_schedule", + "columnsFrom": ["source_user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workflow_schedule_source_workspace_id_workspace_id_fk": { + "name": "workflow_schedule_source_workspace_id_workspace_id_fk", + "tableFrom": "workflow_schedule", + "columnsFrom": ["source_workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_subflows": { + "name": "workflow_subflows", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_subflows_workflow_id_idx": { + "name": "workflow_subflows_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_subflows_workflow_type_idx": { + "name": "workflow_subflows_workflow_type_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "workflow_subflows_workflow_id_workflow_id_fk": { + "name": "workflow_subflows_workflow_id_workflow_id_fk", + "tableFrom": "workflow_subflows", + "columnsFrom": ["workflow_id"], + "tableTo": "workflow", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace": { + "name": "workspace", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'#33C482'" + }, + "logo_url": { + "name": "logo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_mode": { + "name": "workspace_mode", + "type": "workspace_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'grandfathered_shared'" + }, + "billed_account_user_id": { + "name": "billed_account_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "storage_used_bytes": { + "name": "storage_used_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "allow_personal_api_keys": { + "name": "allow_personal_api_keys", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "inbox_enabled": { + "name": "inbox_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "inbox_address": { + "name": "inbox_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "inbox_provider_id": { + "name": "inbox_provider_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "inbox_secret_scope": { + "name": "inbox_secret_scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'all'" + }, + "inbox_mounted_secrets": { + "name": "inbox_mounted_secrets", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "organization_assigned_at": { + "name": "organization_assigned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "forked_from_workspace_id": { + "name": "forked_from_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_owner_id_idx": { + "name": "workspace_owner_id_idx", + "columns": [ + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_organization_id_idx": { + "name": "workspace_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_mode_idx": { + "name": "workspace_mode_idx", + "columns": [ + { + "expression": "workspace_mode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_forked_from_workspace_id_idx": { + "name": "workspace_forked_from_workspace_id_idx", + "columns": [ + { + "expression": "forked_from_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_inbox_provider_id_idx": { + "name": "workspace_inbox_provider_id_idx", + "columns": [ + { + "expression": "inbox_provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"workspace\".\"inbox_provider_id\" IS NOT NULL", + "concurrently": false + } + }, + "foreignKeys": { + "workspace_owner_id_user_id_fk": { + "name": "workspace_owner_id_user_id_fk", + "tableFrom": "workspace", + "columnsFrom": ["owner_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workspace_organization_id_organization_id_fk": { + "name": "workspace_organization_id_organization_id_fk", + "tableFrom": "workspace", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + }, + "workspace_billed_account_user_id_user_id_fk": { + "name": "workspace_billed_account_user_id_user_id_fk", + "tableFrom": "workspace", + "columnsFrom": ["billed_account_user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "no action" + }, + "workspace_forked_from_workspace_id_workspace_id_fk": { + "name": "workspace_forked_from_workspace_id_workspace_id_fk", + "tableFrom": "workspace", + "columnsFrom": ["forked_from_workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "workspace_storage_used_bytes_non_negative": { + "name": "workspace_storage_used_bytes_non_negative", + "value": "\"workspace\".\"storage_used_bytes\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.workspace_byok_keys": { + "name": "workspace_byok_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "encrypted_api_key": { + "name": "encrypted_api_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_byok_workspace_provider_idx": { + "name": "workspace_byok_workspace_provider_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "workspace_byok_keys_workspace_id_workspace_id_fk": { + "name": "workspace_byok_keys_workspace_id_workspace_id_fk", + "tableFrom": "workspace_byok_keys", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workspace_byok_keys_created_by_user_id_fk": { + "name": "workspace_byok_keys_created_by_user_id_fk", + "tableFrom": "workspace_byok_keys", + "columnsFrom": ["created_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_environment": { + "name": "workspace_environment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "variables": { + "name": "variables", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_environment_workspace_unique": { + "name": "workspace_environment_workspace_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "workspace_environment_workspace_id_workspace_id_fk": { + "name": "workspace_environment_workspace_id_workspace_id_fk", + "tableFrom": "workspace_environment", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file": { + "name": "workspace_file", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "uploaded_by": { + "name": "uploaded_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_file_workspace_id_idx": { + "name": "workspace_file_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_file_deleted_at_idx": { + "name": "workspace_file_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_file_workspace_deleted_partial_idx": { + "name": "workspace_file_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"workspace_file\".\"deleted_at\" IS NOT NULL", + "concurrently": false + } + }, + "foreignKeys": { + "workspace_file_workspace_id_workspace_id_fk": { + "name": "workspace_file_workspace_id_workspace_id_fk", + "tableFrom": "workspace_file", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workspace_file_uploaded_by_user_id_fk": { + "name": "workspace_file_uploaded_by_user_id_fk", + "tableFrom": "workspace_file", + "columnsFrom": ["uploaded_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workspace_file_key_unique": { + "name": "workspace_file_key_unique", + "columns": ["key"], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_collab_state": { + "name": "workspace_file_collab_state", + "schema": "", + "columns": { + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "doc_state": { + "name": "doc_state", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "source_hash": { + "name": "source_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workspace_file_collab_state_file_id_workspace_files_id_fk": { + "name": "workspace_file_collab_state_file_id_workspace_files_id_fk", + "tableFrom": "workspace_file_collab_state", + "columnsFrom": ["file_id"], + "tableTo": "workspace_files", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_search_backfill": { + "name": "workspace_file_search_backfill", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "after_workspace_id": { + "name": "after_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "after_file_id": { + "name": "after_file_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_search_dispatch_queue": { + "name": "workspace_file_search_dispatch_queue", + "schema": "", + "columns": { + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "enqueued_at": { + "name": "enqueued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_dispatched_at": { + "name": "last_dispatched_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_file_search_dispatch_queue_schedule_idx": { + "name": "workspace_file_search_dispatch_queue_schedule_idx", + "columns": [ + { + "expression": "last_dispatched_at", + "isExpression": false, + "asc": true, + "nulls": "first" + }, + { + "expression": "enqueued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "workspace_file_search_queue_workspace_fk": { + "name": "workspace_file_search_queue_workspace_fk", + "tableFrom": "workspace_file_search_dispatch_queue", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_search_index": { + "name": "workspace_file_search_index", + "schema": "", + "columns": { + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_content_updated_at": { + "name": "source_content_updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "workspace_file_search_index_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "partial": { + "name": "partial", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "line_count": { + "name": "line_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "indexed_bytes": { + "name": "indexed_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "dispatched_at": { + "name": "dispatched_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_file_search_index_workspace_status_idx": { + "name": "workspace_file_search_index_workspace_status_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_content_updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_file_search_index_pending_dispatch_idx": { + "name": "workspace_file_search_index_pending_dispatch_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_content_updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"workspace_file_search_index\".\"status\" = 'pending' AND \"workspace_file_search_index\".\"dispatched_at\" IS NULL", + "concurrently": false + }, + "workspace_file_search_index_active_dispatch_idx": { + "name": "workspace_file_search_index_active_dispatch_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "dispatched_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"workspace_file_search_index\".\"status\" = 'pending' AND \"workspace_file_search_index\".\"dispatched_at\" IS NOT NULL", + "concurrently": false + } + }, + "foreignKeys": { + "workspace_file_search_index_file_fk": { + "name": "workspace_file_search_index_file_fk", + "tableFrom": "workspace_file_search_index", + "columnsFrom": ["file_id"], + "tableTo": "workspace_files", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workspace_file_search_index_workspace_fk": { + "name": "workspace_file_search_index_workspace_fk", + "tableFrom": "workspace_file_search_index", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "workspace_file_search_index_pk": { + "name": "workspace_file_search_index_pk", + "columns": ["file_id", "source_content_updated_at"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_search_segment": { + "name": "workspace_file_search_segment", + "schema": "", + "columns": { + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_content_updated_at": { + "name": "source_content_updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "line_number": { + "name": "line_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "segment_number": { + "name": "segment_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "segment_start": { + "name": "segment_start", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "line_length": { + "name": "line_length", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "workspace_file_search_segment_workspace_revision_idx": { + "name": "workspace_file_search_segment_workspace_revision_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_content_updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_file_search_segment_workspace_content_trgm_idx": { + "name": "workspace_file_search_segment_workspace_content_trgm_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "text_ops" + }, + { + "expression": "content", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "with": {}, + "method": "gin", + "concurrently": false + } + }, + "foreignKeys": { + "workspace_file_search_segment_file_fk": { + "name": "workspace_file_search_segment_file_fk", + "tableFrom": "workspace_file_search_segment", + "columnsFrom": ["file_id"], + "tableTo": "workspace_files", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workspace_file_search_segment_workspace_fk": { + "name": "workspace_file_search_segment_workspace_fk", + "tableFrom": "workspace_file_search_segment", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "workspace_file_search_segment_pk": { + "name": "workspace_file_search_segment_pk", + "columns": ["file_id", "source_content_updated_at", "line_number", "segment_number"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_secret_provenance": { + "name": "workspace_file_secret_provenance", + "schema": "", + "columns": { + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "content_updated_at": { + "name": "content_updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entries": { + "name": "entries", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workspace_file_secret_provenance_file_id_workspace_files_id_fk": { + "name": "workspace_file_secret_provenance_file_id_workspace_files_id_fk", + "tableFrom": "workspace_file_secret_provenance", + "columnsFrom": ["file_id"], + "tableTo": "workspace_files", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "workspace_file_secret_provenance_status_check": { + "name": "workspace_file_secret_provenance_status_check", + "value": "\"workspace_file_secret_provenance\".\"status\" IN ('exact', 'unknown', 'unrecorded')" + } + }, + "isRLSEnabled": false + }, + "public.workspace_files": { + "name": "workspace_files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "folder_id": { + "name": "folder_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "context": { + "name": "context", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "original_name": { + "name": "original_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size_bytes": { + "name": "size_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "width": { + "name": "width", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "height": { + "name": "height", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "content_updated_at": { + "name": "content_updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "secret_provenance_version": { + "name": "secret_provenance_version", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workspace_files_key_active_unique": { + "name": "workspace_files_key_active_unique", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"workspace_files\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "workspace_files_workspace_folder_name_active_unique": { + "name": "workspace_files_workspace_folder_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"folder_id\", '')", + "isExpression": true, + "asc": true, + "nulls": "last" + }, + { + "expression": "original_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"workspace_files\".\"deleted_at\" IS NULL AND \"workspace_files\".\"context\" = 'workspace' AND \"workspace_files\".\"workspace_id\" IS NOT NULL", + "concurrently": false + }, + "workspace_files_chat_display_name_unique": { + "name": "workspace_files_chat_display_name_unique", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"workspace_files\".\"context\" = 'mothership' AND \"workspace_files\".\"chat_id\" IS NOT NULL", + "concurrently": false + }, + "workspace_files_organization_id_idx": { + "name": "workspace_files_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_files_key_idx": { + "name": "workspace_files_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_files_user_id_idx": { + "name": "workspace_files_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_files_workspace_id_idx": { + "name": "workspace_files_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_files_folder_id_idx": { + "name": "workspace_files_folder_id_idx", + "columns": [ + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_files_context_idx": { + "name": "workspace_files_context_idx", + "columns": [ + { + "expression": "context", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_files_chat_id_idx": { + "name": "workspace_files_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_files_deleted_at_idx": { + "name": "workspace_files_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_files_workspace_deleted_partial_idx": { + "name": "workspace_files_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"workspace_files\".\"deleted_at\" IS NOT NULL", + "concurrently": false + } + }, + "foreignKeys": { + "workspace_files_user_id_user_id_fk": { + "name": "workspace_files_user_id_user_id_fk", + "tableFrom": "workspace_files", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workspace_files_workspace_id_workspace_id_fk": { + "name": "workspace_files_workspace_id_workspace_id_fk", + "tableFrom": "workspace_files", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workspace_files_organization_id_organization_id_fk": { + "name": "workspace_files_organization_id_organization_id_fk", + "tableFrom": "workspace_files", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workspace_files_folder_id_folder_id_fk": { + "name": "workspace_files_folder_id_folder_id_fk", + "tableFrom": "workspace_files", + "columnsFrom": ["folder_id"], + "tableTo": "folder", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + }, + "workspace_files_chat_id_copilot_chats_id_fk": { + "name": "workspace_files_chat_id_copilot_chats_id_fk", + "tableFrom": "workspace_files", + "columnsFrom": ["chat_id"], + "tableTo": "copilot_chats", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "workspace_files_organization_binding_check": { + "name": "workspace_files_organization_binding_check", + "value": "\"workspace_files\".\"organization_id\" IS NULL OR (\"workspace_files\".\"workspace_id\" IS NULL AND \"workspace_files\".\"context\" = 'knowledge-base' AND \"workspace_files\".\"folder_id\" IS NULL AND \"workspace_files\".\"chat_id\" IS NULL)" + } + }, + "isRLSEnabled": false + }, + "public.workspace_fork_block_map": { + "name": "workspace_fork_block_map", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "child_workspace_id": { + "name": "child_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_workflow_id": { + "name": "parent_workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_block_id": { + "name": "parent_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "child_workflow_id": { + "name": "child_workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "child_block_id": { + "name": "child_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_fork_block_map_child_ws_parent_unique": { + "name": "workspace_fork_block_map_child_ws_parent_unique", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_fork_block_map_child_ws_child_unique": { + "name": "workspace_fork_block_map_child_ws_child_unique", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "child_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_fork_block_map_child_ws_parent_wf_idx": { + "name": "workspace_fork_block_map_child_ws_parent_wf_idx", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_fork_block_map_child_ws_child_wf_idx": { + "name": "workspace_fork_block_map_child_ws_child_wf_idx", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "child_workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "workspace_fork_block_map_child_workspace_id_workspace_id_fk": { + "name": "workspace_fork_block_map_child_workspace_id_workspace_id_fk", + "tableFrom": "workspace_fork_block_map", + "columnsFrom": ["child_workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_fork_dependent_value": { + "name": "workspace_fork_dependent_value", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "child_workspace_id": { + "name": "child_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_workflow_id": { + "name": "target_workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_block_id": { + "name": "target_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sub_block_key": { + "name": "sub_block_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_fork_dependent_value_child_ws_wf_idx": { + "name": "workspace_fork_dependent_value_child_ws_wf_idx", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_fork_dependent_value_field_unique": { + "name": "workspace_fork_dependent_value_field_unique", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sub_block_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "workspace_fork_dependent_value_child_workspace_id_workspace_id_fk": { + "name": "workspace_fork_dependent_value_child_workspace_id_workspace_id_fk", + "tableFrom": "workspace_fork_dependent_value", + "columnsFrom": ["child_workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_fork_promote_run": { + "name": "workspace_fork_promote_run", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "child_workspace_id": { + "name": "child_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_workspace_id": { + "name": "source_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_workspace_id": { + "name": "target_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "direction": { + "name": "direction", + "type": "workspace_fork_promote_direction", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "snapshot": { + "name": "snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_fork_promote_run_child_ws_target_unique": { + "name": "workspace_fork_promote_run_child_ws_target_unique", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_fork_promote_run_target_ws_idx": { + "name": "workspace_fork_promote_run_target_ws_idx", + "columns": [ + { + "expression": "target_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "workspace_fork_promote_run_child_workspace_id_workspace_id_fk": { + "name": "workspace_fork_promote_run_child_workspace_id_workspace_id_fk", + "tableFrom": "workspace_fork_promote_run", + "columnsFrom": ["child_workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workspace_fork_promote_run_created_by_user_id_fk": { + "name": "workspace_fork_promote_run_created_by_user_id_fk", + "tableFrom": "workspace_fork_promote_run", + "columnsFrom": ["created_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_fork_resource_map": { + "name": "workspace_fork_resource_map", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "child_workspace_id": { + "name": "child_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "workspace_fork_resource_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "parent_resource_id": { + "name": "parent_resource_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "child_resource_id": { + "name": "child_resource_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_fork_resource_map_child_ws_idx": { + "name": "workspace_fork_resource_map_child_ws_idx", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_fork_resource_map_child_ws_type_idx": { + "name": "workspace_fork_resource_map_child_ws_type_idx", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_fork_resource_map_child_type_parent_unique": { + "name": "workspace_fork_resource_map_child_type_parent_unique", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "workspace_fork_resource_map_child_workspace_id_workspace_id_fk": { + "name": "workspace_fork_resource_map_child_workspace_id_workspace_id_fk", + "tableFrom": "workspace_fork_resource_map", + "columnsFrom": ["child_workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workspace_fork_resource_map_created_by_user_id_fk": { + "name": "workspace_fork_resource_map_created_by_user_id_fk", + "tableFrom": "workspace_fork_resource_map", + "columnsFrom": ["created_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_operation_receipt": { + "name": "workspace_operation_receipt", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_hash": { + "name": "request_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "report": { + "name": "report", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_operation_receipt_request_unique": { + "name": "workspace_operation_receipt_request_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_operation_receipt_workspace_created_idx": { + "name": "workspace_operation_receipt_workspace_created_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "workspace_operation_receipt_workspace_id_workspace_id_fk": { + "name": "workspace_operation_receipt_workspace_id_workspace_id_fk", + "tableFrom": "workspace_operation_receipt", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_sandbox": { + "name": "workspace_sandbox", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "language": { + "name": "language", + "type": "sandbox_language", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "dependencies": { + "name": "dependencies", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "cli_tools": { + "name": "cli_tools", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "system_packages": { + "name": "system_packages", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "spec_hash": { + "name": "spec_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_sandbox_workspace_name_unique": { + "name": "workspace_sandbox_workspace_name_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_sandbox_workspace_idx": { + "name": "workspace_sandbox_workspace_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_sandbox_spec_hash_idx": { + "name": "workspace_sandbox_spec_hash_idx", + "columns": [ + { + "expression": "spec_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "workspace_sandbox_workspace_id_workspace_id_fk": { + "name": "workspace_sandbox_workspace_id_workspace_id_fk", + "tableFrom": "workspace_sandbox", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workspace_sandbox_created_by_user_id_fk": { + "name": "workspace_sandbox_created_by_user_id_fk", + "tableFrom": "workspace_sandbox", + "columnsFrom": ["created_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.academy_cert_status": { + "name": "academy_cert_status", + "schema": "public", + "values": ["active", "revoked", "expired"] + }, + "public.background_work_kind": { + "name": "background_work_kind", + "schema": "public", + "values": ["deployment_side_effects", "fork_content_copy", "fork_sync", "fork_rollback"] + }, + "public.background_work_status_value": { + "name": "background_work_status_value", + "schema": "public", + "values": ["pending", "processing", "completed", "completed_with_warnings", "failed"] + }, + "public.billing_blocked_reason": { + "name": "billing_blocked_reason", + "schema": "public", + "values": ["payment_failed", "dispute"] + }, + "public.billing_entity_type": { + "name": "billing_entity_type", + "schema": "public", + "values": ["user", "organization"] + }, + "public.chat_type": { + "name": "chat_type", + "schema": "public", + "values": ["mothership", "copilot"] + }, + "public.copilot_async_tool_status": { + "name": "copilot_async_tool_status", + "schema": "public", + "values": ["pending", "running", "completed", "failed", "cancelled", "delivered"] + }, + "public.copilot_run_status": { + "name": "copilot_run_status", + "schema": "public", + "values": ["active", "paused_waiting_for_tool", "resuming", "complete", "error", "cancelled"] + }, + "public.copilot_tool_permission_decision": { + "name": "copilot_tool_permission_decision", + "schema": "public", + "values": ["allow", "allow_chat", "always_allow", "skip"] + }, + "public.credential_group_enrollment_status": { + "name": "credential_group_enrollment_status", + "schema": "public", + "values": ["invited", "delivery_failed", "in_progress", "completed", "revoked"] + }, + "public.credential_group_status": { + "name": "credential_group_status", + "schema": "public", + "values": ["active", "disabled"] + }, + "public.credential_member_role": { + "name": "credential_member_role", + "schema": "public", + "values": ["admin", "member"] + }, + "public.credential_member_status": { + "name": "credential_member_status", + "schema": "public", + "values": ["active", "pending", "revoked"] + }, + "public.credential_type": { + "name": "credential_type", + "schema": "public", + "values": [ + "oauth", + "managed_oauth", + "managed_mcp", + "env_workspace", + "env_personal", + "service_account", + "personal_token" + ] + }, + "public.data_drain_cadence": { + "name": "data_drain_cadence", + "schema": "public", + "values": ["hourly", "daily"] + }, + "public.data_drain_destination": { + "name": "data_drain_destination", + "schema": "public", + "values": ["s3", "gcs", "azure_blob", "datadog", "bigquery", "snowflake", "webhook"] + }, + "public.data_drain_run_status": { + "name": "data_drain_run_status", + "schema": "public", + "values": ["running", "success", "failed"] + }, + "public.data_drain_run_trigger": { + "name": "data_drain_run_trigger", + "schema": "public", + "values": ["cron", "manual"] + }, + "public.data_drain_source": { + "name": "data_drain_source", + "schema": "public", + "values": ["workflow_logs", "job_logs", "audit_logs", "copilot_chats", "copilot_runs"] + }, + "public.execution_large_value_reference_source": { + "name": "execution_large_value_reference_source", + "schema": "public", + "values": ["execution_log", "paused_snapshot"] + }, + "public.folder_resource_type": { + "name": "folder_resource_type", + "schema": "public", + "values": ["workflow", "file", "knowledge_base", "table"] + }, + "public.invitation_kind": { + "name": "invitation_kind", + "schema": "public", + "values": ["organization", "workspace"] + }, + "public.invitation_membership_intent": { + "name": "invitation_membership_intent", + "schema": "public", + "values": ["internal", "external"] + }, + "public.invitation_status": { + "name": "invitation_status", + "schema": "public", + "values": ["pending", "accepted", "rejected", "cancelled", "expired"] + }, + "public.managed_oauth_credential_status": { + "name": "managed_oauth_credential_status", + "schema": "public", + "values": ["active", "needs_reauth", "revoked"] + }, + "public.permission_type": { + "name": "permission_type", + "schema": "public", + "values": ["admin", "write", "read"] + }, + "public.sandbox_image_status": { + "name": "sandbox_image_status", + "schema": "public", + "values": ["pending", "building", "ready", "failed"] + }, + "public.sandbox_language": { + "name": "sandbox_language", + "schema": "public", + "values": ["javascript", "python"] + }, + "public.secret_usage_scope": { + "name": "secret_usage_scope", + "schema": "public", + "values": ["workspace", "personal"] + }, + "public.secret_usage_source": { + "name": "secret_usage_source", + "schema": "public", + "values": ["workflow", "copilot", "mcp"] + }, + "public.upload_session_method": { + "name": "upload_session_method", + "schema": "public", + "values": ["put", "multipart"] + }, + "public.upload_session_provider": { + "name": "upload_session_provider", + "schema": "public", + "values": ["local", "s3", "blob", "gcs"] + }, + "public.upload_session_purpose": { + "name": "upload_session_purpose", + "schema": "public", + "values": [ + "workspace_file", + "table_import", + "knowledge_document", + "profile_picture", + "workspace_logo", + "organization_logo", + "mothership_attachment", + "execution_attachment" + ] + }, + "public.upload_session_status": { + "name": "upload_session_status", + "schema": "public", + "values": [ + "uploading", + "completing", + "finalizing", + "completed", + "aborting", + "aborted", + "failed", + "expired" + ] + }, + "public.usage_log_category": { + "name": "usage_log_category", + "schema": "public", + "values": ["model", "fixed", "tool", "model_unbilled"] + }, + "public.usage_log_source": { + "name": "usage_log_source", + "schema": "public", + "values": [ + "workflow", + "wand", + "copilot", + "workspace-chat", + "mcp_copilot", + "mothership_block", + "knowledge-base", + "voice-input", + "enrichment", + "voice-output", + "api-tool" + ] + }, + "public.workspace_file_search_index_status": { + "name": "workspace_file_search_index_status", + "schema": "public", + "values": ["pending", "ready", "skipped", "failed"] + }, + "public.workspace_fork_promote_direction": { + "name": "workspace_fork_promote_direction", + "schema": "public", + "values": ["push", "pull"] + }, + "public.workspace_fork_resource_type": { + "name": "workspace_fork_resource_type", + "schema": "public", + "values": [ + "workflow", + "oauth_credential", + "service_account_credential", + "env_var", + "table", + "knowledge_base", + "knowledge_document", + "file", + "file_folder", + "mcp_server", + "workflow_mcp_server", + "custom_block", + "custom_tool", + "skill", + "sandbox" + ] + }, + "public.workspace_mode": { + "name": "workspace_mode", + "schema": "public", + "values": ["personal", "organization", "grandfathered_shared"] + } + }, + "schemas": {}, + "views": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/packages/db/migrations/meta/_journal.json b/packages/db/migrations/meta/_journal.json index 032acfbe213..4957be9db64 100644 --- a/packages/db/migrations/meta/_journal.json +++ b/packages/db/migrations/meta/_journal.json @@ -2493,6 +2493,13 @@ "when": 1789672641125, "tag": "0356_connector_partition_progress", "breakpoints": true + }, + { + "idx": 357, + "version": "7", + "when": 1789677432855, + "tag": "0357_outbox_autovacuum", + "breakpoints": true } ] } From 7931dcc6b05fce2071cc69ea421e3f6c03db7c07 Mon Sep 17 00:00:00 2001 From: Waleed Date: Thu, 17 Sep 2026 14:10:11 -0700 Subject: [PATCH 14/15] fix(file-search): store workspace file revisions at millisecond precision (#7941) * fix(file-search): store workspace file revisions at millisecond precision The content revision round-trips through JavaScript Date and JSON, both of which truncate to milliseconds, while now() stores microseconds. A sub-millisecond revision stops comparing equal to its own round-trip, so every SQL equality keyed on it matches zero rows and the file is never claimed, indexed, or cleaned up. Truncate at the column default and enforce it with a BEFORE trigger for the writers a default cannot reach, then retire the rows minted before the invariant existed in a batched script migration. * fix(file-search): normalize revisions promoted by metadata-only writes A chat upload is materialized into a workspace file by setting context alone, so the revision is never written and the search-index trigger would key the promoted file to a value nothing can claim. Fire the normalizer on every update; `UPDATE OF` is evaluated against the statement's target columns, so this does not demote provenance on a metadata write. Accept the shared integration database in the repair suite the way the sibling suites do, and record the new script migration in the registry order assertions. * chore(db): drop the 0357 migration ahead of renumbering * chore(db): renumber the content revision migration to 0358 Staging took 0357 for the outbox autovacuum migration. The SQL is byte for byte the previously reviewed file; only the number and the references to it changed. --- .github/workflows/test-build.yml | 5 +- ...rkspace_file_content_version_precision.sql | 29 + .../db/migrations/meta/0358_snapshot.json | 27509 ++++++++++++++++ packages/db/migrations/meta/_journal.json | 7 + packages/db/schema.ts | 14 +- ...rations-paused-billing-attribution.test.ts | 1 + ...6_backfill_search_vectors.postgres.test.ts | 1 + ...ace_file_content_revision.postgres.test.ts | 320 + ..._repair_workspace_file_content_revision.ts | 142 + packages/db/script-migrations/index.ts | 3 + 10 files changed, 28029 insertions(+), 2 deletions(-) create mode 100644 packages/db/migrations/0358_workspace_file_content_version_precision.sql create mode 100644 packages/db/migrations/meta/0358_snapshot.json create mode 100644 packages/db/script-migrations/0018_repair_workspace_file_content_revision.postgres.test.ts create mode 100644 packages/db/script-migrations/0018_repair_workspace_file_content_revision.ts diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml index c7c8b5de7b2..833228f7931 100644 --- a/.github/workflows/test-build.yml +++ b/.github/workflows/test-build.yml @@ -222,7 +222,10 @@ jobs: working-directory: packages/db env: KNOWLEDGE_ACL_TEST_DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/sim_auth_scim - run: bunx vitest run script-migrations/0016_backfill_search_vectors.postgres.test.ts + run: >- + bunx vitest run + script-migrations/0016_backfill_search_vectors.postgres.test.ts + script-migrations/0018_repair_workspace_file_content_revision.postgres.test.ts - name: Verify Search progress, pagination, and outbox scheduling in PostgreSQL working-directory: apps/sim diff --git a/packages/db/migrations/0358_workspace_file_content_version_precision.sql b/packages/db/migrations/0358_workspace_file_content_version_precision.sql new file mode 100644 index 00000000000..4e8b04b727c --- /dev/null +++ b/packages/db/migrations/0358_workspace_file_content_version_precision.sql @@ -0,0 +1,29 @@ +ALTER TABLE "workspace_files" ALTER COLUMN "content_updated_at" SET DEFAULT date_trunc('milliseconds', now());--> statement-breakpoint +-- The trigger is the invariant; the truncating default above only keeps the common insert path from +-- paying for it. A default cannot cover the writers that matter here: explicit `CURRENT_TIMESTAMP` +-- expressions and raw SQL inserts. +-- +-- It fires on every UPDATE rather than `UPDATE OF content_updated_at`, because a row can enter the +-- search index without its revision being written: materializing a chat upload sets `context` alone, and +-- `workspace_files_search_index_pending` then indexes whatever revision the row already carried. `UPDATE +-- OF` is evaluated against the statement's target columns, so widening it does not make +-- `workspace_files_secret_provenance_demote` fire on those metadata writes — normalizing a legacy row +-- keeps its tracked provenance. The WHEN guard keeps the plpgsql call off the hot path for the compliant +-- writes that are the norm, and the name sorts before both other triggers so they see the normalized +-- value. Rows minted before this runs are retired by script migration +-- `0018_repair_workspace_file_content_revision`, which the runner applies after every SQL migration. +CREATE OR REPLACE FUNCTION workspace_file_content_version_millisecond() +RETURNS trigger +LANGUAGE plpgsql +AS $$ +BEGIN + NEW.content_updated_at := date_trunc('milliseconds', NEW.content_updated_at); + RETURN NEW; +END; +$$;--> statement-breakpoint +DROP TRIGGER IF EXISTS workspace_files_content_version_millisecond ON workspace_files;--> statement-breakpoint +CREATE TRIGGER workspace_files_content_version_millisecond +BEFORE INSERT OR UPDATE ON workspace_files +FOR EACH ROW +WHEN (NEW.content_updated_at <> date_trunc('milliseconds', NEW.content_updated_at)) +EXECUTE FUNCTION workspace_file_content_version_millisecond(); diff --git a/packages/db/migrations/meta/0358_snapshot.json b/packages/db/migrations/meta/0358_snapshot.json new file mode 100644 index 00000000000..d5c5d7607fc --- /dev/null +++ b/packages/db/migrations/meta/0358_snapshot.json @@ -0,0 +1,27509 @@ +{ + "id": "8ce471af-c0e7-4903-a99f-03038e99d8e6", + "prevId": "be1f2311-29ef-4e97-a7a8-cd008b64f8a5", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.academy_certificate": { + "name": "academy_certificate", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "course_id": { + "name": "course_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "academy_cert_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "issued_at": { + "name": "issued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "certificate_number": { + "name": "certificate_number", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "academy_certificate_user_id_idx": { + "name": "academy_certificate_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "academy_certificate_course_id_idx": { + "name": "academy_certificate_course_id_idx", + "columns": [ + { + "expression": "course_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "academy_certificate_user_course_unique": { + "name": "academy_certificate_user_course_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "course_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "academy_certificate_status_idx": { + "name": "academy_certificate_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "academy_certificate_user_id_user_id_fk": { + "name": "academy_certificate_user_id_user_id_fk", + "tableFrom": "academy_certificate", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "academy_certificate_certificate_number_unique": { + "name": "academy_certificate_certificate_number_unique", + "nullsNotDistinct": false, + "columns": ["certificate_number"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oauth_config": { + "name": "oauth_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "account_user_id_idx": { + "name": "account_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_account_on_account_id_provider_id": { + "name": "idx_account_on_account_id_provider_id", + "columns": [ + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "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.api_key": { + "name": "api_key", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'personal'" + }, + "last_used": { + "name": "last_used", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "api_key_workspace_type_idx": { + "name": "api_key_workspace_type_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "api_key_user_type_idx": { + "name": "api_key_user_type_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "api_key_key_hash_idx": { + "name": "api_key_key_hash_idx", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "api_key_user_id_user_id_fk": { + "name": "api_key_user_id_user_id_fk", + "tableFrom": "api_key", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "api_key_workspace_id_workspace_id_fk": { + "name": "api_key_workspace_id_workspace_id_fk", + "tableFrom": "api_key", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "api_key_created_by_user_id_fk": { + "name": "api_key_created_by_user_id_fk", + "tableFrom": "api_key", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "api_key_key_unique": { + "name": "api_key_key_unique", + "nullsNotDistinct": false, + "columns": ["key"] + } + }, + "policies": {}, + "checkConstraints": { + "workspace_type_check": { + "name": "workspace_type_check", + "value": "(type = 'workspace' AND workspace_id IS NOT NULL) OR (type = 'personal' AND workspace_id IS NULL)" + } + }, + "isRLSEnabled": false + }, + "public.async_jobs": { + "name": "async_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "run_at": { + "name": "run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "max_attempts": { + "name": "max_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 3 + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "output": { + "name": "output", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "async_jobs_status_started_at_idx": { + "name": "async_jobs_status_started_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "async_jobs_status_completed_at_idx": { + "name": "async_jobs_status_completed_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "completed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "async_jobs_schedule_pending_run_at_idx": { + "name": "async_jobs_schedule_pending_run_at_idx", + "columns": [ + { + "expression": "run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"async_jobs\".\"type\" = 'schedule-execution' AND \"async_jobs\".\"status\" = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "async_jobs_schedule_processing_started_at_idx": { + "name": "async_jobs_schedule_processing_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"async_jobs\".\"type\" = 'schedule-execution' AND \"async_jobs\".\"status\" = 'processing'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "async_jobs_schedule_unreconciled_terminal_idx": { + "name": "async_jobs_schedule_unreconciled_terminal_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"async_jobs\".\"type\" = 'schedule-execution' AND \"async_jobs\".\"status\" IN ('completed', 'failed', 'cancelled') AND COALESCE(\"async_jobs\".\"metadata\" ->> 'scheduleReconciled', 'false') <> 'true'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.audit_log": { + "name": "audit_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_name": { + "name": "actor_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_email": { + "name": "actor_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resource_name": { + "name": "resource_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "surface": { + "name": "surface", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "audit_log_workspace_created_idx": { + "name": "audit_log_workspace_created_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_log_workspace_created_at_id_idx": { + "name": "audit_log_workspace_created_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"created_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_log_actor_created_idx": { + "name": "audit_log_actor_created_idx", + "columns": [ + { + "expression": "actor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_log_resource_idx": { + "name": "audit_log_resource_idx", + "columns": [ + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_log_action_idx": { + "name": "audit_log_action_idx", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "audit_log_workspace_id_workspace_id_fk": { + "name": "audit_log_workspace_id_workspace_id_fk", + "tableFrom": "audit_log", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "audit_log_actor_id_user_id_fk": { + "name": "audit_log_actor_id_user_id_fk", + "tableFrom": "audit_log", + "tableTo": "user", + "columnsFrom": ["actor_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.background_work_status": { + "name": "background_work_status", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "background_work_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "background_work_status_value", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "background_work_status_workspace_status_idx": { + "name": "background_work_status_workspace_status_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "background_work_status_workflow_status_idx": { + "name": "background_work_status_workflow_status_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "background_work_status_meta_child_ws_idx": { + "name": "background_work_status_meta_child_ws_idx", + "columns": [ + { + "expression": "(\"metadata\" ->> 'childWorkspaceId')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "background_work_status_meta_other_ws_idx": { + "name": "background_work_status_meta_other_ws_idx", + "columns": [ + { + "expression": "(\"metadata\" ->> 'otherWorkspaceId')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "background_work_status_workspace_id_workspace_id_fk": { + "name": "background_work_status_workspace_id_workspace_id_fk", + "tableFrom": "background_work_status", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "background_work_status_workflow_id_workflow_id_fk": { + "name": "background_work_status_workflow_id_workflow_id_fk", + "tableFrom": "background_work_status", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat": { + "name": "chat", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "customizations": { + "name": "customizations", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'public'" + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "allowed_emails": { + "name": "allowed_emails", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "output_configs": { + "name": "output_configs", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "include_thinking": { + "name": "include_thinking", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "include_tool_calls": { + "name": "include_tool_calls", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "identifier_idx": { + "name": "identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_archived_at_partial_idx": { + "name": "chat_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"chat\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_chat_on_workflow_id_archived_at": { + "name": "idx_chat_on_workflow_id_archived_at", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_workflow_id_workflow_id_fk": { + "name": "chat_workflow_id_workflow_id_fk", + "tableFrom": "chat", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_user_id_user_id_fk": { + "name": "chat_user_id_user_id_fk", + "tableFrom": "chat", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_async_tool_calls": { + "name": "copilot_async_tool_calls", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "checkpoint_id": { + "name": "checkpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "tool_call_id": { + "name": "tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "args": { + "name": "args", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "status": { + "name": "status", + "type": "copilot_async_tool_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "permission_decision": { + "name": "permission_decision", + "type": "copilot_tool_permission_decision", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "permission_decided_at": { + "name": "permission_decided_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "claimed_by": { + "name": "claimed_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_async_tool_calls_run_id_idx": { + "name": "copilot_async_tool_calls_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_checkpoint_id_idx": { + "name": "copilot_async_tool_calls_checkpoint_id_idx", + "columns": [ + { + "expression": "checkpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_status_idx": { + "name": "copilot_async_tool_calls_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_run_status_idx": { + "name": "copilot_async_tool_calls_run_status_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_tool_call_id_unique": { + "name": "copilot_async_tool_calls_tool_call_id_unique", + "columns": [ + { + "expression": "tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_async_tool_calls_run_id_copilot_runs_id_fk": { + "name": "copilot_async_tool_calls_run_id_copilot_runs_id_fk", + "tableFrom": "copilot_async_tool_calls", + "tableTo": "copilot_runs", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_async_tool_calls_checkpoint_id_copilot_run_checkpoints_id_fk": { + "name": "copilot_async_tool_calls_checkpoint_id_copilot_run_checkpoints_id_fk", + "tableFrom": "copilot_async_tool_calls", + "tableTo": "copilot_run_checkpoints", + "columnsFrom": ["checkpoint_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_chats": { + "name": "copilot_chats", + "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 + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "chat_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'copilot'" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'claude-3-7-sonnet-latest'" + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_conversation_key": { + "name": "external_conversation_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_conversation_metadata": { + "name": "external_conversation_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "preview_yaml": { + "name": "preview_yaml", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "plan_artifact": { + "name": "plan_artifact", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "resources": { + "name": "resources", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "auto_allowed_tools": { + "name": "auto_allowed_tools", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "pinned": { + "name": "pinned", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_chats_organization_id_idx": { + "name": "copilot_chats_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_external_conversation_unique": { + "name": "copilot_chats_external_conversation_unique", + "columns": [ + { + "expression": "external_conversation_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"copilot_chats\".\"external_conversation_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_user_org_created_idx": { + "name": "copilot_chats_user_org_created_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_user_id_idx": { + "name": "copilot_chats_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_workflow_id_idx": { + "name": "copilot_chats_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_user_workflow_idx": { + "name": "copilot_chats_user_workflow_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_user_workspace_idx": { + "name": "copilot_chats_user_workspace_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_created_at_idx": { + "name": "copilot_chats_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_updated_at_idx": { + "name": "copilot_chats_updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_workspace_created_at_id_idx": { + "name": "copilot_chats_workspace_created_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"created_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_user_workspace_deleted_partial_idx": { + "name": "copilot_chats_user_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"copilot_chats\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_chats_user_id_user_id_fk": { + "name": "copilot_chats_user_id_user_id_fk", + "tableFrom": "copilot_chats", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_chats_workflow_id_workflow_id_fk": { + "name": "copilot_chats_workflow_id_workflow_id_fk", + "tableFrom": "copilot_chats", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_chats_workspace_id_workspace_id_fk": { + "name": "copilot_chats_workspace_id_workspace_id_fk", + "tableFrom": "copilot_chats", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_chats_organization_id_organization_id_fk": { + "name": "copilot_chats_organization_id_organization_id_fk", + "tableFrom": "copilot_chats", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "copilot_chats_owner_check": { + "name": "copilot_chats_owner_check", + "value": "num_nonnulls(\"copilot_chats\".\"workspace_id\", \"copilot_chats\".\"organization_id\") <= 1" + }, + "copilot_chats_organization_workflow_check": { + "name": "copilot_chats_organization_workflow_check", + "value": "\"copilot_chats\".\"organization_id\" IS NULL OR \"copilot_chats\".\"workflow_id\" IS NULL" + } + }, + "isRLSEnabled": false + }, + "public.copilot_feedback": { + "name": "copilot_feedback", + "schema": "", + "columns": { + "feedback_id": { + "name": "feedback_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_query": { + "name": "user_query", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_response": { + "name": "agent_response", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_positive": { + "name": "is_positive", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "feedback": { + "name": "feedback", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_yaml": { + "name": "workflow_yaml", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_feedback_user_id_idx": { + "name": "copilot_feedback_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_feedback_chat_id_idx": { + "name": "copilot_feedback_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_feedback_user_chat_idx": { + "name": "copilot_feedback_user_chat_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_feedback_is_positive_idx": { + "name": "copilot_feedback_is_positive_idx", + "columns": [ + { + "expression": "is_positive", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_feedback_created_at_idx": { + "name": "copilot_feedback_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_feedback_user_id_user_id_fk": { + "name": "copilot_feedback_user_id_user_id_fk", + "tableFrom": "copilot_feedback", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_feedback_chat_id_copilot_chats_id_fk": { + "name": "copilot_feedback_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_feedback", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_messages": { + "name": "copilot_messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "stream_id": { + "name": "stream_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "parent_message_id": { + "name": "parent_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tokens_in": { + "name": "tokens_in", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "tokens_out": { + "name": "tokens_out", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "seq": { + "name": "seq", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_messages_chat_message_unique": { + "name": "copilot_messages_chat_message_unique", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_messages_chat_created_at_idx": { + "name": "copilot_messages_chat_created_at_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"copilot_messages\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_messages_chat_seq_idx": { + "name": "copilot_messages_chat_seq_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "seq", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"copilot_messages\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_messages_chat_stream_idx": { + "name": "copilot_messages_chat_stream_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stream_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"copilot_messages\".\"stream_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_messages_user_created_at_idx": { + "name": "copilot_messages_user_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"copilot_messages\".\"role\" = 'user' AND \"copilot_messages\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_messages_chat_id_copilot_chats_id_fk": { + "name": "copilot_messages_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_messages", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_run_checkpoints": { + "name": "copilot_run_checkpoints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pending_tool_call_id": { + "name": "pending_tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "conversation_snapshot": { + "name": "conversation_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "agent_state": { + "name": "agent_state", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "provider_request": { + "name": "provider_request", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_run_checkpoints_run_id_idx": { + "name": "copilot_run_checkpoints_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_run_checkpoints_pending_tool_call_id_idx": { + "name": "copilot_run_checkpoints_pending_tool_call_id_idx", + "columns": [ + { + "expression": "pending_tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_run_checkpoints_run_pending_tool_unique": { + "name": "copilot_run_checkpoints_run_pending_tool_unique", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pending_tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_run_checkpoints_run_id_copilot_runs_id_fk": { + "name": "copilot_run_checkpoints_run_id_copilot_runs_id_fk", + "tableFrom": "copilot_run_checkpoints", + "tableTo": "copilot_runs", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_runs": { + "name": "copilot_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_run_id": { + "name": "parent_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stream_id": { + "name": "stream_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent": { + "name": "agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "copilot_run_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "request_context": { + "name": "request_context", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "copilot_runs_execution_id_idx": { + "name": "copilot_runs_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_parent_run_id_idx": { + "name": "copilot_runs_parent_run_id_idx", + "columns": [ + { + "expression": "parent_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_chat_id_idx": { + "name": "copilot_runs_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_chat_started_at_idx": { + "name": "copilot_runs_chat_started_at_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_user_id_idx": { + "name": "copilot_runs_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_workflow_id_idx": { + "name": "copilot_runs_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_workspace_id_idx": { + "name": "copilot_runs_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_status_idx": { + "name": "copilot_runs_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_chat_execution_idx": { + "name": "copilot_runs_chat_execution_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_execution_started_at_idx": { + "name": "copilot_runs_execution_started_at_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_workspace_completed_at_id_idx": { + "name": "copilot_runs_workspace_completed_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"completed_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_stream_id_unique": { + "name": "copilot_runs_stream_id_unique", + "columns": [ + { + "expression": "stream_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_runs_chat_id_copilot_chats_id_fk": { + "name": "copilot_runs_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_runs", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_runs_user_id_user_id_fk": { + "name": "copilot_runs_user_id_user_id_fk", + "tableFrom": "copilot_runs", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_runs_workflow_id_workflow_id_fk": { + "name": "copilot_runs_workflow_id_workflow_id_fk", + "tableFrom": "copilot_runs", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_runs_workspace_id_workspace_id_fk": { + "name": "copilot_runs_workspace_id_workspace_id_fk", + "tableFrom": "copilot_runs", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_workflow_read_hashes": { + "name": "copilot_workflow_read_hashes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_workflow_read_hashes_chat_id_idx": { + "name": "copilot_workflow_read_hashes_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_workflow_read_hashes_workflow_id_idx": { + "name": "copilot_workflow_read_hashes_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_workflow_read_hashes_chat_workflow_unique": { + "name": "copilot_workflow_read_hashes_chat_workflow_unique", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_workflow_read_hashes_chat_id_copilot_chats_id_fk": { + "name": "copilot_workflow_read_hashes_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_workflow_read_hashes", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_workflow_read_hashes_workflow_id_workflow_id_fk": { + "name": "copilot_workflow_read_hashes_workflow_id_workflow_id_fk", + "tableFrom": "copilot_workflow_read_hashes", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.credential": { + "name": "credential", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slack_app_id": { + "name": "slack_app_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "credential_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "unredacted": { + "name": "unredacted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env_key": { + "name": "env_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env_owner_user_id": { + "name": "env_owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_service_account_key": { + "name": "encrypted_service_account_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_personal_token": { + "name": "encrypted_personal_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "authorization_app_id": { + "name": "authorization_app_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_group_enrollment_id": { + "name": "credential_group_enrollment_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_group_option_id": { + "name": "credential_group_option_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mcp_server_id": { + "name": "mcp_server_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mcp_oauth_config_version": { + "name": "mcp_oauth_config_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "managed_oauth_scope_version": { + "name": "managed_oauth_scope_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "provider_subject_id": { + "name": "provider_subject_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_tenant_id": { + "name": "provider_tenant_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "managed_oauth_status": { + "name": "managed_oauth_status", + "type": "managed_oauth_credential_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "granted_scopes": { + "name": "granted_scopes", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "provider_metadata": { + "name": "provider_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "encrypted_oauth_token_set": { + "name": "encrypted_oauth_token_set", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mcp_tools": { + "name": "mcp_tools", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "mcp_tools_refreshed_at": { + "name": "mcp_tools_refreshed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "granted_at": { + "name": "granted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_refreshed_at": { + "name": "last_refreshed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "credential_organization_id_idx": { + "name": "credential_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_organization_account_unique": { + "name": "credential_organization_account_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"credential\".\"account_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_org_personal_token_unique": { + "name": "credential_org_personal_token_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_subject_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"credential\".\"type\" = 'personal_token'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_workspace_id_idx": { + "name": "credential_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_type_idx": { + "name": "credential_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_provider_id_idx": { + "name": "credential_provider_id_idx", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_account_id_idx": { + "name": "credential_account_id_idx", + "columns": [ + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_env_owner_user_id_idx": { + "name": "credential_env_owner_user_id_idx", + "columns": [ + { + "expression": "env_owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_group_enrollment_idx": { + "name": "credential_group_enrollment_idx", + "columns": [ + { + "expression": "credential_group_enrollment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_mcp_server_idx": { + "name": "credential_mcp_server_idx", + "columns": [ + { + "expression": "mcp_server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_group_option_unique": { + "name": "credential_group_option_unique", + "columns": [ + { + "expression": "credential_group_enrollment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "credential_group_option_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"credential\".\"type\" = 'managed_oauth'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_managed_mcp_enrollment_server_unique": { + "name": "credential_managed_mcp_enrollment_server_unique", + "columns": [ + { + "expression": "credential_group_enrollment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "mcp_server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"credential\".\"type\" = 'managed_mcp'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_workspace_account_unique": { + "name": "credential_workspace_account_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "account_id IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_workspace_env_unique": { + "name": "credential_workspace_env_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "type = 'env_workspace'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_workspace_personal_env_unique": { + "name": "credential_workspace_personal_env_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env_owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "type = 'env_personal'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_personal_token_identity_unique": { + "name": "credential_personal_token_identity_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_subject_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "type = 'personal_token'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "credential_workspace_id_workspace_id_fk": { + "name": "credential_workspace_id_workspace_id_fk", + "tableFrom": "credential", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_organization_id_organization_id_fk": { + "name": "credential_organization_id_organization_id_fk", + "tableFrom": "credential", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_slack_app_id_slack_app_id_fk": { + "name": "credential_slack_app_id_slack_app_id_fk", + "tableFrom": "credential", + "tableTo": "slack_app", + "columnsFrom": ["slack_app_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "credential_account_id_account_id_fk": { + "name": "credential_account_id_account_id_fk", + "tableFrom": "credential", + "tableTo": "account", + "columnsFrom": ["account_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_env_owner_user_id_user_id_fk": { + "name": "credential_env_owner_user_id_user_id_fk", + "tableFrom": "credential", + "tableTo": "user", + "columnsFrom": ["env_owner_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_credential_group_enrollment_id_credential_group_enrollment_id_fk": { + "name": "credential_credential_group_enrollment_id_credential_group_enrollment_id_fk", + "tableFrom": "credential", + "tableTo": "credential_group_enrollment", + "columnsFrom": ["credential_group_enrollment_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_mcp_server_id_mcp_servers_id_fk": { + "name": "credential_mcp_server_id_mcp_servers_id_fk", + "tableFrom": "credential", + "tableTo": "mcp_servers", + "columnsFrom": ["mcp_server_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_created_by_user_id_fk": { + "name": "credential_created_by_user_id_fk", + "tableFrom": "credential", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "credential_owner_check": { + "name": "credential_owner_check", + "value": "num_nonnulls(\"credential\".\"workspace_id\", \"credential\".\"organization_id\") = 1" + }, + "credential_organization_type_check": { + "name": "credential_organization_type_check", + "value": "\"credential\".\"organization_id\" IS NULL OR \"credential\".\"type\" IN ('oauth', 'managed_oauth', 'managed_mcp', 'service_account', 'personal_token')" + }, + "credential_personal_token_source_check": { + "name": "credential_personal_token_source_check", + "value": "(type::text <> 'personal_token') OR (\n created_by IS NOT NULL\n AND provider_id IS NOT NULL\n AND provider_id = 'gitlab'\n AND provider_subject_id IS NOT NULL\n AND provider_tenant_id IS NOT NULL\n AND encrypted_personal_token IS NOT NULL\n AND granted_scopes IS NOT NULL\n AND cardinality(granted_scopes) > 0\n AND account_id IS NULL\n AND env_key IS NULL\n AND env_owner_user_id IS NULL\n AND authorization_app_id IS NULL\n AND encrypted_oauth_token_set IS NULL\n AND encrypted_service_account_key IS NULL\n AND unredacted = false\n )" + }, + "credential_oauth_source_check": { + "name": "credential_oauth_source_check", + "value": "(type <> 'oauth') OR (account_id IS NOT NULL AND provider_id IS NOT NULL)" + }, + "credential_managed_oauth_source_check": { + "name": "credential_managed_oauth_source_check", + "value": "(type::text <> 'managed_oauth') OR (\n account_id IS NULL\n AND provider_id IS NOT NULL\n AND authorization_app_id IS NOT NULL\n AND provider_subject_id IS NOT NULL\n AND managed_oauth_status IS NOT NULL\n AND granted_scopes IS NOT NULL\n AND encrypted_oauth_token_set IS NOT NULL\n AND granted_at IS NOT NULL\n )" + }, + "credential_managed_oauth_group_binding_check": { + "name": "credential_managed_oauth_group_binding_check", + "value": "(type::text <> 'managed_oauth') OR (\n credential_group_enrollment_id IS NOT NULL\n AND credential_group_option_id IS NOT NULL\n AND managed_oauth_scope_version IS NOT NULL\n AND managed_oauth_scope_version > 0\n )" + }, + "credential_managed_mcp_source_check": { + "name": "credential_managed_mcp_source_check", + "value": "(type::text <> 'managed_mcp') OR (\n id LIKE 'mcp-cg-%'\n AND account_id IS NULL\n AND provider_id IS NULL\n AND authorization_app_id IS NULL\n AND credential_group_enrollment_id IS NOT NULL\n AND credential_group_option_id IS NULL\n AND mcp_server_id IS NOT NULL\n AND managed_oauth_status IS NOT NULL\n AND (managed_oauth_status <> 'active' OR (\n encrypted_oauth_token_set IS NOT NULL\n AND mcp_tools IS NOT NULL\n ))\n AND granted_at IS NOT NULL\n AND managed_oauth_scope_version IS NULL\n AND provider_subject_id IS NULL\n AND provider_tenant_id IS NULL\n AND granted_scopes IS NULL\n AND provider_metadata IS NULL\n AND created_by IS NULL\n AND env_key IS NULL\n AND env_owner_user_id IS NULL\n AND encrypted_service_account_key IS NULL\n AND unredacted = false\n )" + }, + "credential_creator_source_check": { + "name": "credential_creator_source_check", + "value": "(type::text = 'managed_mcp') OR created_by IS NOT NULL" + }, + "credential_workspace_env_source_check": { + "name": "credential_workspace_env_source_check", + "value": "(type <> 'env_workspace') OR (env_key IS NOT NULL AND env_owner_user_id IS NULL)" + }, + "credential_personal_env_source_check": { + "name": "credential_personal_env_source_check", + "value": "(type <> 'env_personal') OR (env_key IS NOT NULL AND env_owner_user_id IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.credential_group": { + "name": "credential_group", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "public_id": { + "name": "public_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "options": { + "name": "options", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "encrypted_provider_configuration": { + "name": "encrypted_provider_configuration", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "credential_group_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "credential_group_organization_id_idx": { + "name": "credential_group_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_group_organization_unique": { + "name": "credential_group_organization_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_group_public_id_unique": { + "name": "credential_group_public_id_unique", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_group_workspace_unique": { + "name": "credential_group_workspace_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "credential_group_workspace_id_workspace_id_fk": { + "name": "credential_group_workspace_id_workspace_id_fk", + "tableFrom": "credential_group", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_group_organization_id_organization_id_fk": { + "name": "credential_group_organization_id_organization_id_fk", + "tableFrom": "credential_group", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_group_created_by_user_id_fk": { + "name": "credential_group_created_by_user_id_fk", + "tableFrom": "credential_group", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "credential_group_owner_check": { + "name": "credential_group_owner_check", + "value": "num_nonnulls(\"credential_group\".\"workspace_id\", \"credential_group\".\"organization_id\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.credential_group_enrollment": { + "name": "credential_group_enrollment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "credential_group_id": { + "name": "credential_group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "credential_group_enrollment_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'invited'" + }, + "invitation_token_hash": { + "name": "invitation_token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "invitation_expires_at": { + "name": "invitation_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "invited_at": { + "name": "invited_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "sent_at": { + "name": "sent_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_delivery_error": { + "name": "last_delivery_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "credential_group_enrollment_group_user_unique": { + "name": "credential_group_enrollment_group_user_unique", + "columns": [ + { + "expression": "credential_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"credential_group_enrollment\".\"user_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_group_enrollment_user_id_idx": { + "name": "credential_group_enrollment_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_group_enrollment_group_email_unique": { + "name": "credential_group_enrollment_group_email_unique", + "columns": [ + { + "expression": "credential_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_group_enrollment_invitation_token_hash_unique": { + "name": "credential_group_enrollment_invitation_token_hash_unique", + "columns": [ + { + "expression": "invitation_token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_group_enrollment_group_status_idx": { + "name": "credential_group_enrollment_group_status_idx", + "columns": [ + { + "expression": "credential_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_group_enrollment_group_invited_at_id_idx": { + "name": "credential_group_enrollment_group_invited_at_id_idx", + "columns": [ + { + "expression": "credential_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "invited_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "credential_group_enrollment_credential_group_id_credential_group_id_fk": { + "name": "credential_group_enrollment_credential_group_id_credential_group_id_fk", + "tableFrom": "credential_group_enrollment", + "tableTo": "credential_group", + "columnsFrom": ["credential_group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_group_enrollment_user_id_user_id_fk": { + "name": "credential_group_enrollment_user_id_user_id_fk", + "tableFrom": "credential_group_enrollment", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_group_enrollment_created_by_user_id_fk": { + "name": "credential_group_enrollment_created_by_user_id_fk", + "tableFrom": "credential_group_enrollment", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "credential_group_enrollment_normalized_email_check": { + "name": "credential_group_enrollment_normalized_email_check", + "value": "\"credential_group_enrollment\".\"email\" = lower(btrim(\"credential_group_enrollment\".\"email\")) AND length(\"credential_group_enrollment\".\"email\") BETWEEN 3 AND 320" + }, + "credential_group_enrollment_invitation_token_hash_length_check": { + "name": "credential_group_enrollment_invitation_token_hash_length_check", + "value": "length(\"credential_group_enrollment\".\"invitation_token_hash\") = 64" + } + }, + "isRLSEnabled": false + }, + "public.credential_member": { + "name": "credential_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "credential_member_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "status": { + "name": "status", + "type": "credential_member_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "invited_by": { + "name": "invited_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "credential_member_user_id_idx": { + "name": "credential_member_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_member_role_idx": { + "name": "credential_member_role_idx", + "columns": [ + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_member_status_idx": { + "name": "credential_member_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_member_unique": { + "name": "credential_member_unique", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "credential_member_credential_id_credential_id_fk": { + "name": "credential_member_credential_id_credential_id_fk", + "tableFrom": "credential_member", + "tableTo": "credential", + "columnsFrom": ["credential_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_member_user_id_user_id_fk": { + "name": "credential_member_user_id_user_id_fk", + "tableFrom": "credential_member", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_member_invited_by_user_id_fk": { + "name": "credential_member_invited_by_user_id_fk", + "tableFrom": "credential_member", + "tableTo": "user", + "columnsFrom": ["invited_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.custom_block": { + "name": "custom_block", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "icon_url": { + "name": "icon_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "inputs": { + "name": "inputs", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "outputs": { + "name": "outputs", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "trace_child_runs": { + "name": "trace_child_runs", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "custom_block_organization_id_idx": { + "name": "custom_block_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "custom_block_workflow_id_idx": { + "name": "custom_block_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "custom_block_organization_type_unique": { + "name": "custom_block_organization_type_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "custom_block_organization_id_organization_id_fk": { + "name": "custom_block_organization_id_organization_id_fk", + "tableFrom": "custom_block", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "custom_block_workflow_id_workflow_id_fk": { + "name": "custom_block_workflow_id_workflow_id_fk", + "tableFrom": "custom_block", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "custom_block_created_by_user_id_fk": { + "name": "custom_block_created_by_user_id_fk", + "tableFrom": "custom_block", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.custom_tools": { + "name": "custom_tools", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schema": { + "name": "schema", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "custom_tools_workspace_id_idx": { + "name": "custom_tools_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "custom_tools_workspace_title_unique": { + "name": "custom_tools_workspace_title_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "custom_tools_workspace_id_workspace_id_fk": { + "name": "custom_tools_workspace_id_workspace_id_fk", + "tableFrom": "custom_tools", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "custom_tools_user_id_user_id_fk": { + "name": "custom_tools_user_id_user_id_fk", + "tableFrom": "custom_tools", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.data_drain_runs": { + "name": "data_drain_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "drain_id": { + "name": "drain_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "data_drain_run_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "trigger": { + "name": "trigger", + "type": "data_drain_run_trigger", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "rows_exported": { + "name": "rows_exported", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "bytes_written": { + "name": "bytes_written", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cursor_before": { + "name": "cursor_before", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cursor_after": { + "name": "cursor_after", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "locators": { + "name": "locators", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": { + "data_drain_runs_drain_started_idx": { + "name": "data_drain_runs_drain_started_idx", + "columns": [ + { + "expression": "drain_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "data_drain_runs_drain_id_data_drains_id_fk": { + "name": "data_drain_runs_drain_id_data_drains_id_fk", + "tableFrom": "data_drain_runs", + "tableTo": "data_drains", + "columnsFrom": ["drain_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.data_drains": { + "name": "data_drains", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "data_drain_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "destination_type": { + "name": "destination_type", + "type": "data_drain_destination", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "destination_config": { + "name": "destination_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "destination_credentials": { + "name": "destination_credentials", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schedule_cadence": { + "name": "schedule_cadence", + "type": "data_drain_cadence", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "cursor": { + "name": "cursor", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_success_at": { + "name": "last_success_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "data_drains_org_idx": { + "name": "data_drains_org_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "data_drains_due_idx": { + "name": "data_drains_due_idx", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "data_drains_org_name_unique": { + "name": "data_drains_org_name_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "data_drains_organization_id_organization_id_fk": { + "name": "data_drains_organization_id_organization_id_fk", + "tableFrom": "data_drains", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "data_drains_created_by_user_id_fk": { + "name": "data_drains_created_by_user_id_fk", + "tableFrom": "data_drains", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.docs_embeddings": { + "name": "docs_embeddings", + "schema": "", + "columns": { + "chunk_id": { + "name": "chunk_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "chunk_text": { + "name": "chunk_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_document": { + "name": "source_document", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_link": { + "name": "source_link", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "header_text": { + "name": "header_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "header_level": { + "name": "header_level", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "embedding": { + "name": "embedding", + "type": "vector(1536)", + "primaryKey": false, + "notNull": true + }, + "embedding_model": { + "name": "embedding_model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text-embedding-3-small'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "chunk_text_tsv": { + "name": "chunk_text_tsv", + "type": "tsvector", + "primaryKey": false, + "notNull": false, + "generated": { + "as": "to_tsvector('english', \"docs_embeddings\".\"chunk_text\")", + "type": "stored" + } + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "docs_emb_source_document_idx": { + "name": "docs_emb_source_document_idx", + "columns": [ + { + "expression": "source_document", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_header_level_idx": { + "name": "docs_emb_header_level_idx", + "columns": [ + { + "expression": "header_level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_source_header_idx": { + "name": "docs_emb_source_header_idx", + "columns": [ + { + "expression": "source_document", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "header_level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_model_idx": { + "name": "docs_emb_model_idx", + "columns": [ + { + "expression": "embedding_model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_created_at_idx": { + "name": "docs_emb_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_embedding_vector_hnsw_idx": { + "name": "docs_embedding_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "docs_emb_metadata_gin_idx": { + "name": "docs_emb_metadata_gin_idx", + "columns": [ + { + "expression": "metadata", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "docs_emb_chunk_text_fts_idx": { + "name": "docs_emb_chunk_text_fts_idx", + "columns": [ + { + "expression": "chunk_text_tsv", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "docs_embedding_not_null_check": { + "name": "docs_embedding_not_null_check", + "value": "\"embedding\" IS NOT NULL" + }, + "docs_header_level_check": { + "name": "docs_header_level_check", + "value": "\"header_level\" >= 1 AND \"header_level\" <= 6" + } + }, + "isRLSEnabled": false + }, + "public.document": { + "name": "document", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "filename": { + "name": "filename", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_url": { + "name": "file_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "storage_key": { + "name": "storage_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "file_size": { + "name": "file_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chunk_count": { + "name": "chunk_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "character_count": { + "name": "character_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "processing_status": { + "name": "processing_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "processing_attempts": { + "name": "processing_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "processing_queued_at": { + "name": "processing_queued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "processing_queue_token": { + "name": "processing_queue_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "processing_started_at": { + "name": "processing_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "processing_deferred_until": { + "name": "processing_deferred_until", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "processing_completed_at": { + "name": "processing_completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "processing_error": { + "name": "processing_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "processing_recovery_after": { + "name": "processing_recovery_after", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "user_excluded": { + "name": "user_excluded", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "tag1": { + "name": "tag1", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag2": { + "name": "tag2", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag3": { + "name": "tag3", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag4": { + "name": "tag4", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag5": { + "name": "tag5", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag6": { + "name": "tag6", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag7": { + "name": "tag7", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "number1": { + "name": "number1", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number2": { + "name": "number2", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number3": { + "name": "number3", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number4": { + "name": "number4", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number5": { + "name": "number5", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "date1": { + "name": "date1", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "date2": { + "name": "date2", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "boolean1": { + "name": "boolean1", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean2": { + "name": "boolean2", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean3": { + "name": "boolean3", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_url": { + "name": "source_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_provenance_version": { + "name": "secret_provenance_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "uploaded_by": { + "name": "uploaded_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "acl": { + "name": "acl", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{ws}'::text[]" + }, + "acl_requirements": { + "name": "acl_requirements", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "acl_verified_at": { + "name": "acl_verified_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "source_modified_at": { + "name": "source_modified_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "source_seen_at": { + "name": "source_seen_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "doc_kb_id_idx": { + "name": "doc_kb_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_acl_gin_idx": { + "name": "doc_acl_gin_idx", + "columns": [ + { + "expression": "acl", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "array_ops" + } + ], + "isUnique": false, + "where": "\"document\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "gin", + "with": {} + }, + "doc_filename_idx": { + "name": "doc_filename_idx", + "columns": [ + { + "expression": "filename", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_processing_status_idx": { + "name": "doc_processing_status_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "processing_status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_processing_recovery_idx": { + "name": "doc_processing_recovery_idx", + "columns": [ + { + "expression": "uploaded_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"document\".\"processing_status\" IN ('pending', 'processing', 'failed') AND \"document\".\"connector_id\" IS NOT NULL AND \"document\".\"content_hash\" IS NOT NULL AND \"document\".\"storage_key\" IS NOT NULL AND \"document\".\"user_excluded\" = false AND \"document\".\"archived_at\" IS NULL AND \"document\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_connector_processing_status_idx": { + "name": "doc_connector_processing_status_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "processing_status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"document\".\"processing_status\" IN ('pending', 'processing', 'failed') AND \"document\".\"connector_id\" IS NOT NULL AND \"document\".\"user_excluded\" = false AND \"document\".\"archived_at\" IS NULL AND \"document\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_connector_external_id_idx": { + "name": "doc_connector_external_id_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"document\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_connector_source_lookup_idx": { + "name": "doc_connector_source_lookup_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_connector_reconciliation_idx": { + "name": "doc_connector_reconciliation_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "COALESCE(\"source_seen_at\", '-infinity'::timestamp)", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"document\".\"user_excluded\" = false AND \"document\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_active_kb_token_count_idx": { + "name": "doc_active_kb_token_count_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "token_count", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"document\".\"user_excluded\" = false AND \"document\".\"archived_at\" IS NULL AND \"document\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_storage_key_idx": { + "name": "doc_storage_key_idx", + "columns": [ + { + "expression": "storage_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"document\".\"storage_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_archived_at_partial_idx": { + "name": "doc_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"document\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_deleted_at_partial_idx": { + "name": "doc_deleted_at_partial_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"document\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_kb_tag1_lower_idx": { + "name": "doc_kb_tag1_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag1\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_kb_tag2_lower_idx": { + "name": "doc_kb_tag2_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag2\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_kb_tag3_lower_idx": { + "name": "doc_kb_tag3_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag3\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_kb_tag4_lower_idx": { + "name": "doc_kb_tag4_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag4\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_kb_tag5_lower_idx": { + "name": "doc_kb_tag5_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag5\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_kb_tag6_lower_idx": { + "name": "doc_kb_tag6_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag6\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_kb_tag7_lower_idx": { + "name": "doc_kb_tag7_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag7\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number1_idx": { + "name": "doc_number1_idx", + "columns": [ + { + "expression": "number1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number2_idx": { + "name": "doc_number2_idx", + "columns": [ + { + "expression": "number2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number3_idx": { + "name": "doc_number3_idx", + "columns": [ + { + "expression": "number3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number4_idx": { + "name": "doc_number4_idx", + "columns": [ + { + "expression": "number4", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number5_idx": { + "name": "doc_number5_idx", + "columns": [ + { + "expression": "number5", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_date1_idx": { + "name": "doc_date1_idx", + "columns": [ + { + "expression": "date1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_date2_idx": { + "name": "doc_date2_idx", + "columns": [ + { + "expression": "date2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean1_idx": { + "name": "doc_boolean1_idx", + "columns": [ + { + "expression": "boolean1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean2_idx": { + "name": "doc_boolean2_idx", + "columns": [ + { + "expression": "boolean2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean3_idx": { + "name": "doc_boolean3_idx", + "columns": [ + { + "expression": "boolean3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_knowledge_base_id_knowledge_base_id_fk": { + "name": "document_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "document", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_connector_id_knowledge_connector_id_fk": { + "name": "document_connector_id_knowledge_connector_id_fk", + "tableFrom": "document", + "tableTo": "knowledge_connector", + "columnsFrom": ["connector_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_uploaded_by_user_id_fk": { + "name": "document_uploaded_by_user_id_fk", + "tableFrom": "document", + "tableTo": "user", + "columnsFrom": ["uploaded_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "doc_acl_token_shape_check": { + "name": "doc_acl_token_shape_check", + "value": "array_position(\"document\".\"acl\", NULL) IS NULL AND (cardinality(\"document\".\"acl\") = 0 OR (cardinality(\"document\".\"acl\") = array_length(string_to_array(array_to_string(\"document\".\"acl\", E'\\n'), E'\\n'), 1) AND array_to_string(\"document\".\"acl\", E'\\n') ~ '^((ws|pub|link|u:[^\\nA-Z]+@[^\\nA-Z]+|[gs]:[^\\n:]+:[^\\n:]+:[^\\n]+)(\\n(ws|pub|link|u:[^\\nA-Z]+@[^\\nA-Z]+|[gs]:[^\\n:]+:[^\\n:]+:[^\\n]+))*)$'))" + } + }, + "isRLSEnabled": false + }, + "public.document_secret_provenance": { + "name": "document_secret_provenance", + "schema": "", + "columns": { + "document_id": { + "name": "document_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "source_hash": { + "name": "source_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entries": { + "name": "entries", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "document_secret_provenance_document_id_document_id_fk": { + "name": "document_secret_provenance_document_id_document_id_fk", + "tableFrom": "document_secret_provenance", + "tableTo": "document", + "columnsFrom": ["document_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "document_secret_provenance_status_check": { + "name": "document_secret_provenance_status_check", + "value": "\"document_secret_provenance\".\"status\" IN ('exact', 'unknown')" + } + }, + "isRLSEnabled": false + }, + "public.embedding": { + "name": "embedding", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chunk_index": { + "name": "chunk_index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "chunk_hash": { + "name": "chunk_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret_provenance_version": { + "name": "secret_provenance_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "content_length": { + "name": "content_length", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "embedding": { + "name": "embedding", + "type": "vector(1536)", + "primaryKey": false, + "notNull": false + }, + "embedding_384": { + "name": "embedding_384", + "type": "vector(384)", + "primaryKey": false, + "notNull": false + }, + "embedding_768": { + "name": "embedding_768", + "type": "vector(768)", + "primaryKey": false, + "notNull": false + }, + "embedding_1024": { + "name": "embedding_1024", + "type": "vector(1024)", + "primaryKey": false, + "notNull": false + }, + "embedding_3072": { + "name": "embedding_3072", + "type": "vector(3072)", + "primaryKey": false, + "notNull": false + }, + "embedding_model": { + "name": "embedding_model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text-embedding-3-small'" + }, + "start_offset": { + "name": "start_offset", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "end_offset": { + "name": "end_offset", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "tag1": { + "name": "tag1", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag2": { + "name": "tag2", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag3": { + "name": "tag3", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag4": { + "name": "tag4", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag5": { + "name": "tag5", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag6": { + "name": "tag6", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag7": { + "name": "tag7", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "number1": { + "name": "number1", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number2": { + "name": "number2", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number3": { + "name": "number3", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number4": { + "name": "number4", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number5": { + "name": "number5", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "date1": { + "name": "date1", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "date2": { + "name": "date2", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "boolean1": { + "name": "boolean1", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean2": { + "name": "boolean2", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean3": { + "name": "boolean3", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "content_tsv": { + "name": "content_tsv", + "type": "tsvector", + "primaryKey": false, + "notNull": false, + "generated": { + "as": "to_tsvector('english', \"embedding\".\"content\")", + "type": "stored" + } + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "emb_kb_id_idx": { + "name": "emb_kb_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_id_idx": { + "name": "emb_doc_id_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_chunk_idx": { + "name": "emb_doc_chunk_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chunk_index", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_model_idx": { + "name": "emb_kb_model_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "embedding_model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_enabled_idx": { + "name": "emb_kb_enabled_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_enabled_idx": { + "name": "emb_doc_enabled_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "embedding_vector_hnsw_idx": { + "name": "embedding_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_384_vector_hnsw_idx": { + "name": "embedding_384_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding_384", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_768_vector_hnsw_idx": { + "name": "embedding_768_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding_768", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_1024_vector_hnsw_idx": { + "name": "embedding_1024_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding_1024", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_3072_vector_hnsw_idx": { + "name": "embedding_3072_vector_hnsw_idx", + "columns": [ + { + "expression": "(\"embedding_3072\"::halfvec(3072)) halfvec_cosine_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_binary_hnsw_idx": { + "name": "embedding_binary_hnsw_idx", + "columns": [ + { + "expression": "(binary_quantize(\"embedding\")::bit(1536)) bit_hamming_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_384_binary_hnsw_idx": { + "name": "embedding_384_binary_hnsw_idx", + "columns": [ + { + "expression": "(binary_quantize(\"embedding_384\")::bit(384)) bit_hamming_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_768_binary_hnsw_idx": { + "name": "embedding_768_binary_hnsw_idx", + "columns": [ + { + "expression": "(binary_quantize(\"embedding_768\")::bit(768)) bit_hamming_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_1024_binary_hnsw_idx": { + "name": "embedding_1024_binary_hnsw_idx", + "columns": [ + { + "expression": "(binary_quantize(\"embedding_1024\")::bit(1024)) bit_hamming_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_3072_binary_hnsw_idx": { + "name": "embedding_3072_binary_hnsw_idx", + "columns": [ + { + "expression": "(binary_quantize(\"embedding_3072\")::bit(3072)) bit_hamming_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "emb_kb_tag1_lower_idx": { + "name": "emb_kb_tag1_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag1\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_tag2_lower_idx": { + "name": "emb_kb_tag2_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag2\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_tag3_lower_idx": { + "name": "emb_kb_tag3_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag3\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_tag4_lower_idx": { + "name": "emb_kb_tag4_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag4\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_tag5_lower_idx": { + "name": "emb_kb_tag5_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag5\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_tag6_lower_idx": { + "name": "emb_kb_tag6_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag6\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_tag7_lower_idx": { + "name": "emb_kb_tag7_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag7\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number1_idx": { + "name": "emb_number1_idx", + "columns": [ + { + "expression": "number1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number2_idx": { + "name": "emb_number2_idx", + "columns": [ + { + "expression": "number2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number3_idx": { + "name": "emb_number3_idx", + "columns": [ + { + "expression": "number3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number4_idx": { + "name": "emb_number4_idx", + "columns": [ + { + "expression": "number4", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number5_idx": { + "name": "emb_number5_idx", + "columns": [ + { + "expression": "number5", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_date1_idx": { + "name": "emb_date1_idx", + "columns": [ + { + "expression": "date1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_date2_idx": { + "name": "emb_date2_idx", + "columns": [ + { + "expression": "date2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean1_idx": { + "name": "emb_boolean1_idx", + "columns": [ + { + "expression": "boolean1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean2_idx": { + "name": "emb_boolean2_idx", + "columns": [ + { + "expression": "boolean2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean3_idx": { + "name": "emb_boolean3_idx", + "columns": [ + { + "expression": "boolean3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_content_fts_idx": { + "name": "emb_content_fts_idx", + "columns": [ + { + "expression": "content_tsv", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "embedding_knowledge_base_id_knowledge_base_id_fk": { + "name": "embedding_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "embedding", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "embedding_document_id_document_id_fk": { + "name": "embedding_document_id_document_id_fk", + "tableFrom": "embedding", + "tableTo": "document", + "columnsFrom": ["document_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "embedding_width_check": { + "name": "embedding_width_check", + "value": "num_nonnulls(\"embedding\", \"embedding_384\", \"embedding_768\", \"embedding_1024\", \"embedding_3072\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.embedding_keyword_search": { + "name": "embedding_keyword_search", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "content_tsv": { + "name": "content_tsv", + "type": "tsvector", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "embedding_keyword_search_kb_idx": { + "name": "embedding_keyword_search_kb_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "embedding_keyword_search_document_idx": { + "name": "embedding_keyword_search_document_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "embedding_keyword_search_content_idx": { + "name": "embedding_keyword_search_content_idx", + "columns": [ + { + "expression": "content_tsv", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "embedding_keyword_search_id_embedding_id_fk": { + "name": "embedding_keyword_search_id_embedding_id_fk", + "tableFrom": "embedding_keyword_search", + "tableTo": "embedding", + "columnsFrom": ["id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.embedding_search": { + "name": "embedding_search", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "binary": { + "name": "binary", + "type": "bit(1536)", + "primaryKey": false, + "notNull": false + }, + "binary_384": { + "name": "binary_384", + "type": "bit(384)", + "primaryKey": false, + "notNull": false + }, + "binary_768": { + "name": "binary_768", + "type": "bit(768)", + "primaryKey": false, + "notNull": false + }, + "binary_1024": { + "name": "binary_1024", + "type": "bit(1024)", + "primaryKey": false, + "notNull": false + }, + "binary_3072": { + "name": "binary_3072", + "type": "bit(3072)", + "primaryKey": false, + "notNull": false + }, + "vector": { + "name": "vector", + "type": "halfvec(1536)", + "primaryKey": false, + "notNull": false + }, + "vector_384": { + "name": "vector_384", + "type": "halfvec(384)", + "primaryKey": false, + "notNull": false + }, + "vector_512": { + "name": "vector_512", + "type": "halfvec(512)", + "primaryKey": false, + "notNull": false + }, + "vector_768": { + "name": "vector_768", + "type": "halfvec(768)", + "primaryKey": false, + "notNull": false + }, + "vector_1024": { + "name": "vector_1024", + "type": "halfvec(1024)", + "primaryKey": false, + "notNull": false + }, + "vector_3072": { + "name": "vector_3072", + "type": "halfvec(3072)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "embedding_search_kb_idx": { + "name": "embedding_search_kb_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "embedding_search_document_lookup_idx": { + "name": "embedding_search_document_lookup_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"embedding_search\".\"enabled\"", + "concurrently": true, + "method": "btree", + "with": {} + }, + "embedding_search_binary_hnsw_idx": { + "name": "embedding_search_binary_hnsw_idx", + "columns": [ + { + "expression": "binary", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "bit_hamming_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_search_384_binary_hnsw_idx": { + "name": "embedding_search_384_binary_hnsw_idx", + "columns": [ + { + "expression": "binary_384", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "bit_hamming_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_search_768_binary_hnsw_idx": { + "name": "embedding_search_768_binary_hnsw_idx", + "columns": [ + { + "expression": "binary_768", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "bit_hamming_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_search_1024_binary_hnsw_idx": { + "name": "embedding_search_1024_binary_hnsw_idx", + "columns": [ + { + "expression": "binary_1024", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "bit_hamming_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_search_3072_binary_hnsw_idx": { + "name": "embedding_search_3072_binary_hnsw_idx", + "columns": [ + { + "expression": "binary_3072", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "bit_hamming_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_search_cosine_hnsw_idx": { + "name": "embedding_search_cosine_hnsw_idx", + "columns": [ + { + "expression": "vector", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "halfvec_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_search_512_cosine_hnsw_idx": { + "name": "embedding_search_512_cosine_hnsw_idx", + "columns": [ + { + "expression": "vector_512", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "halfvec_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_search_384_cosine_hnsw_idx": { + "name": "embedding_search_384_cosine_hnsw_idx", + "columns": [ + { + "expression": "vector_384", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "halfvec_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_search_768_cosine_hnsw_idx": { + "name": "embedding_search_768_cosine_hnsw_idx", + "columns": [ + { + "expression": "vector_768", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "halfvec_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_search_1024_cosine_hnsw_idx": { + "name": "embedding_search_1024_cosine_hnsw_idx", + "columns": [ + { + "expression": "vector_1024", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "halfvec_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_search_3072_cosine_hnsw_idx": { + "name": "embedding_search_3072_cosine_hnsw_idx", + "columns": [ + { + "expression": "vector_3072", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "halfvec_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + } + }, + "foreignKeys": { + "embedding_search_id_embedding_id_fk": { + "name": "embedding_search_id_embedding_id_fk", + "tableFrom": "embedding_search", + "tableTo": "embedding", + "columnsFrom": ["id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "embedding_search_width_check": { + "name": "embedding_search_width_check", + "value": "num_nonnulls(\"binary\", \"binary_384\", \"binary_768\", \"binary_1024\", \"binary_3072\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.embedding_secret_provenance": { + "name": "embedding_secret_provenance", + "schema": "", + "columns": { + "embedding_id": { + "name": "embedding_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entries": { + "name": "entries", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "embedding_secret_provenance_embedding_id_embedding_id_fk": { + "name": "embedding_secret_provenance_embedding_id_embedding_id_fk", + "tableFrom": "embedding_secret_provenance", + "tableTo": "embedding", + "columnsFrom": ["embedding_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "embedding_secret_provenance_status_check": { + "name": "embedding_secret_provenance_status_check", + "value": "\"embedding_secret_provenance\".\"status\" IN ('exact', 'unknown')" + } + }, + "isRLSEnabled": false + }, + "public.environment": { + "name": "environment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "variables": { + "name": "variables", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "environment_user_id_user_id_fk": { + "name": "environment_user_id_user_id_fk", + "tableFrom": "environment", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "environment_user_id_unique": { + "name": "environment_user_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_large_value_dependencies": { + "name": "execution_large_value_dependencies", + "schema": "", + "columns": { + "parent_key": { + "name": "parent_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "child_key": { + "name": "child_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_large_value_dependencies_workspace_parent_key_idx": { + "name": "execution_large_value_dependencies_workspace_parent_key_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_large_value_dependencies_workspace_child_key_idx": { + "name": "execution_large_value_dependencies_workspace_child_key_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "child_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_large_value_dependencies_workspace_id_workspace_id_fk": { + "name": "execution_large_value_dependencies_workspace_id_workspace_id_fk", + "tableFrom": "execution_large_value_dependencies", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "execution_large_value_dependencies_parent_key_child_key_pk": { + "name": "execution_large_value_dependencies_parent_key_child_key_pk", + "columns": ["parent_key", "child_key"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_large_value_references": { + "name": "execution_large_value_references", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "execution_large_value_reference_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_large_value_references_workspace_execution_source_idx": { + "name": "execution_large_value_references_workspace_execution_source_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_large_value_references_workflow_id_idx": { + "name": "execution_large_value_references_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_large_value_references_workspace_id_workspace_id_fk": { + "name": "execution_large_value_references_workspace_id_workspace_id_fk", + "tableFrom": "execution_large_value_references", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_large_value_references_workflow_id_workflow_id_fk": { + "name": "execution_large_value_references_workflow_id_workflow_id_fk", + "tableFrom": "execution_large_value_references", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "execution_large_value_references_key_execution_id_source_pk": { + "name": "execution_large_value_references_key_execution_id_source_pk", + "columns": ["key", "execution_id", "source"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_large_values": { + "name": "execution_large_values", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_execution_id": { + "name": "owner_execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "execution_large_values_owner_execution_id_idx": { + "name": "execution_large_values_owner_execution_id_idx", + "columns": [ + { + "expression": "owner_execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_large_values_cleanup_idx": { + "name": "execution_large_values_cleanup_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"execution_large_values\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_large_values_tombstone_cleanup_idx": { + "name": "execution_large_values_tombstone_cleanup_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"execution_large_values\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_large_values_workflow_id_idx": { + "name": "execution_large_values_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_large_values_workspace_id_workspace_id_fk": { + "name": "execution_large_values_workspace_id_workspace_id_fk", + "tableFrom": "execution_large_values", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_large_values_workflow_id_workflow_id_fk": { + "name": "execution_large_values_workflow_id_workflow_id_fk", + "tableFrom": "execution_large_values", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.folder": { + "name": "folder", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "folder_resource_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "locked": { + "name": "locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "folder_user_idx": { + "name": "folder_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "folder_workspace_resource_parent_idx": { + "name": "folder_workspace_resource_parent_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "folder_parent_sort_idx": { + "name": "folder_parent_sort_idx", + "columns": [ + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "folder_deleted_at_idx": { + "name": "folder_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "folder_workspace_deleted_partial_idx": { + "name": "folder_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"folder\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "folder_workspace_resource_parent_name_active_unique": { + "name": "folder_workspace_resource_parent_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"parent_id\", '')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"folder\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "folder_user_id_user_id_fk": { + "name": "folder_user_id_user_id_fk", + "tableFrom": "folder", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "folder_workspace_id_workspace_id_fk": { + "name": "folder_workspace_id_workspace_id_fk", + "tableFrom": "folder", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "folder_parent_id_folder_id_fk": { + "name": "folder_parent_id_folder_id_fk", + "tableFrom": "folder", + "tableTo": "folder", + "columnsFrom": ["parent_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.idempotency_key": { + "name": "idempotency_key", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "result": { + "name": "result", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idempotency_key_created_at_idx": { + "name": "idempotency_key_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invitation": { + "name": "invitation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "invitation_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'organization'" + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "inviter_id": { + "name": "inviter_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "membership_intent": { + "name": "membership_intent", + "type": "invitation_membership_intent", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'internal'" + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "invitation_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "invitation_email_idx": { + "name": "invitation_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_organization_id_idx": { + "name": "invitation_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_status_idx": { + "name": "invitation_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_pending_email_org_unique": { + "name": "invitation_pending_email_org_unique", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"invitation\".\"status\" = 'pending' AND \"invitation\".\"organization_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invitation_inviter_id_user_id_fk": { + "name": "invitation_inviter_id_user_id_fk", + "tableFrom": "invitation", + "tableTo": "user", + "columnsFrom": ["inviter_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitation_organization_id_organization_id_fk": { + "name": "invitation_organization_id_organization_id_fk", + "tableFrom": "invitation", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "invitation_token_unique": { + "name": "invitation_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invitation_workspace_grant": { + "name": "invitation_workspace_grant", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "invitation_id": { + "name": "invitation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission": { + "name": "permission", + "type": "permission_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "invitation_workspace_grant_unique": { + "name": "invitation_workspace_grant_unique", + "columns": [ + { + "expression": "invitation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_workspace_grant_workspace_id_idx": { + "name": "invitation_workspace_grant_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invitation_workspace_grant_invitation_id_invitation_id_fk": { + "name": "invitation_workspace_grant_invitation_id_invitation_id_fk", + "tableFrom": "invitation_workspace_grant", + "tableTo": "invitation", + "columnsFrom": ["invitation_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitation_workspace_grant_workspace_id_workspace_id_fk": { + "name": "invitation_workspace_grant_workspace_id_workspace_id_fk", + "tableFrom": "invitation_workspace_grant", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.job_execution_logs": { + "name": "job_execution_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "schedule_id": { + "name": "schedule_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "total_duration_ms": { + "name": "total_duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "execution_data": { + "name": "execution_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "cost": { + "name": "cost", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "job_execution_logs_schedule_id_idx": { + "name": "job_execution_logs_schedule_id_idx", + "columns": [ + { + "expression": "schedule_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "job_execution_logs_workspace_started_at_idx": { + "name": "job_execution_logs_workspace_started_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "job_execution_logs_workspace_ended_at_id_idx": { + "name": "job_execution_logs_workspace_ended_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"ended_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "job_execution_logs_execution_id_unique": { + "name": "job_execution_logs_execution_id_unique", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "job_execution_logs_trigger_idx": { + "name": "job_execution_logs_trigger_idx", + "columns": [ + { + "expression": "trigger", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "job_execution_logs_schedule_id_workflow_schedule_id_fk": { + "name": "job_execution_logs_schedule_id_workflow_schedule_id_fk", + "tableFrom": "job_execution_logs", + "tableTo": "workflow_schedule", + "columnsFrom": ["schedule_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "job_execution_logs_workspace_id_workspace_id_fk": { + "name": "job_execution_logs_workspace_id_workspace_id_fk", + "tableFrom": "job_execution_logs", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_base": { + "name": "knowledge_base", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "folder_id": { + "name": "folder_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_search_index": { + "name": "is_search_index", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "embedding_model": { + "name": "embedding_model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text-embedding-3-small'" + }, + "embedding_dimension": { + "name": "embedding_dimension", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1536 + }, + "chunking_config": { + "name": "chunking_config", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{\"maxSize\": 1024, \"minSize\": 1, \"overlap\": 200}'" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "kb_organization_id_idx": { + "name": "kb_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_organization_search_index_unique": { + "name": "kb_organization_search_index_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"knowledge_base\".\"is_search_index\" = true AND \"knowledge_base\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_organization_name_active_unique": { + "name": "kb_organization_name_active_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"knowledge_base\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_user_id_idx": { + "name": "kb_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_workspace_id_idx": { + "name": "kb_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_user_workspace_idx": { + "name": "kb_user_workspace_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_folder_id_idx": { + "name": "kb_folder_id_idx", + "columns": [ + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_deleted_at_idx": { + "name": "kb_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_workspace_deleted_partial_idx": { + "name": "kb_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"knowledge_base\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_workspace_name_active_unique": { + "name": "kb_workspace_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"knowledge_base\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_workspace_search_index_unique": { + "name": "kb_workspace_search_index_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"knowledge_base\".\"is_search_index\" = true AND \"knowledge_base\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_base_user_id_user_id_fk": { + "name": "knowledge_base_user_id_user_id_fk", + "tableFrom": "knowledge_base", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knowledge_base_workspace_id_workspace_id_fk": { + "name": "knowledge_base_workspace_id_workspace_id_fk", + "tableFrom": "knowledge_base", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knowledge_base_organization_id_organization_id_fk": { + "name": "knowledge_base_organization_id_organization_id_fk", + "tableFrom": "knowledge_base", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knowledge_base_folder_id_folder_id_fk": { + "name": "knowledge_base_folder_id_folder_id_fk", + "tableFrom": "knowledge_base", + "tableTo": "folder", + "columnsFrom": ["folder_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kb_owner_check": { + "name": "kb_owner_check", + "value": "num_nonnulls(\"knowledge_base\".\"workspace_id\", \"knowledge_base\".\"organization_id\") = 1" + }, + "kb_organization_search_index_check": { + "name": "kb_organization_search_index_check", + "value": "\"knowledge_base\".\"organization_id\" IS NULL OR \"knowledge_base\".\"is_search_index\"" + }, + "kb_organization_folder_check": { + "name": "kb_organization_folder_check", + "value": "\"knowledge_base\".\"organization_id\" IS NULL OR \"knowledge_base\".\"folder_id\" IS NULL" + } + }, + "isRLSEnabled": false + }, + "public.knowledge_base_tag_definitions": { + "name": "knowledge_base_tag_definitions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tag_slot": { + "name": "tag_slot", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "field_type": { + "name": "field_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "kb_tag_definitions_kb_slot_idx": { + "name": "kb_tag_definitions_kb_slot_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tag_slot", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_tag_definitions_kb_display_name_idx": { + "name": "kb_tag_definitions_kb_display_name_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_tag_definitions_kb_id_idx": { + "name": "kb_tag_definitions_kb_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_base_tag_definitions_knowledge_base_id_knowledge_base_id_fk": { + "name": "knowledge_base_tag_definitions_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "knowledge_base_tag_definitions", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_connector": { + "name": "knowledge_connector", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connector_type": { + "name": "connector_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_api_key": { + "name": "encrypted_api_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_config": { + "name": "source_config", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "sync_mode": { + "name": "sync_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'full'" + }, + "sync_interval_minutes": { + "name": "sync_interval_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1440 + }, + "access_mode": { + "name": "access_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'workspace'" + }, + "credential_group_id": { + "name": "credential_group_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_group_option_id": { + "name": "credential_group_option_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "member_sync_status": { + "name": "member_sync_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "member_sync_lock_token": { + "name": "member_sync_lock_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "member_sync_lock_lease_at": { + "name": "member_sync_lock_lease_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "next_member_sync_at": { + "name": "next_member_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_member_sync_at": { + "name": "last_member_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_member_sync_error": { + "name": "last_member_sync_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "member_sync_consecutive_failures": { + "name": "member_sync_consecutive_failures", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "access_rewrite_pending": { + "name": "access_rewrite_pending", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_sync_error": { + "name": "last_sync_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_sync_doc_count": { + "name": "last_sync_doc_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "listing_checkpoint": { + "name": "listing_checkpoint", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "directory_checkpoint": { + "name": "directory_checkpoint", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "next_sync_at": { + "name": "next_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "next_directory_sync_at": { + "name": "next_directory_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "consecutive_failures": { + "name": "consecutive_failures", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "sync_lock_token": { + "name": "sync_lock_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sync_lock_lease_at": { + "name": "sync_lock_lease_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "kc_knowledge_base_id_idx": { + "name": "kc_knowledge_base_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kc_status_next_sync_idx": { + "name": "kc_status_next_sync_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_sync_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kc_archived_at_partial_idx": { + "name": "kc_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"knowledge_connector\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "kc_deleted_at_partial_idx": { + "name": "kc_deleted_at_partial_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"knowledge_connector\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "kc_member_sync_due_idx": { + "name": "kc_member_sync_due_idx", + "columns": [ + { + "expression": "member_sync_status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_member_sync_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"knowledge_connector\".\"access_mode\" = 'members' AND \"knowledge_connector\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "kc_directory_sync_due_idx": { + "name": "kc_directory_sync_due_idx", + "columns": [ + { + "expression": "next_directory_sync_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"knowledge_connector\".\"access_mode\" = 'admin' AND \"knowledge_connector\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_connector_knowledge_base_id_knowledge_base_id_fk": { + "name": "knowledge_connector_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "knowledge_connector", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knowledge_connector_credential_group_id_credential_group_id_fk": { + "name": "knowledge_connector_credential_group_id_credential_group_id_fk", + "tableFrom": "knowledge_connector", + "tableTo": "credential_group", + "columnsFrom": ["credential_group_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kc_access_mode_check": { + "name": "kc_access_mode_check", + "value": "\"knowledge_connector\".\"access_mode\" IN ('workspace', 'members', 'admin')" + }, + "kc_member_sync_status_check": { + "name": "kc_member_sync_status_check", + "value": "\"knowledge_connector\".\"member_sync_status\" IN ('idle', 'pending', 'running', 'error', 'disabled')" + }, + "kc_sync_lock_exclusive_check": { + "name": "kc_sync_lock_exclusive_check", + "value": "NOT (\"knowledge_connector\".\"sync_lock_token\" IS NOT NULL AND \"knowledge_connector\".\"member_sync_lock_token\" IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.knowledge_connector_member": { + "name": "knowledge_connector_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_token": { + "name": "subject_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "consecutive_failures": { + "name": "consecutive_failures", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_started_at": { + "name": "last_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_complete_listing_at": { + "name": "last_complete_listing_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_listed_count": { + "name": "last_listed_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "member_synced_through": { + "name": "member_synced_through", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "change_cursor": { + "name": "change_cursor", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "listing_checkpoint": { + "name": "listing_checkpoint", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "suspended_at": { + "name": "suspended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "kcm_organization_id_idx": { + "name": "kcm_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kcm_connector_credential_unique": { + "name": "kcm_connector_credential_unique", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kcm_connector_queue_idx": { + "name": "kcm_connector_queue_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "first" + }, + { + "expression": "last_started_at", + "isExpression": false, + "asc": true, + "nulls": "first" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kcm_credential_idx": { + "name": "kcm_credential_idx", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_connector_member_workspace_id_workspace_id_fk": { + "name": "knowledge_connector_member_workspace_id_workspace_id_fk", + "tableFrom": "knowledge_connector_member", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knowledge_connector_member_organization_id_organization_id_fk": { + "name": "knowledge_connector_member_organization_id_organization_id_fk", + "tableFrom": "knowledge_connector_member", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knowledge_connector_member_connector_id_knowledge_connector_id_fk": { + "name": "knowledge_connector_member_connector_id_knowledge_connector_id_fk", + "tableFrom": "knowledge_connector_member", + "tableTo": "knowledge_connector", + "columnsFrom": ["connector_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knowledge_connector_member_credential_id_credential_id_fk": { + "name": "knowledge_connector_member_credential_id_credential_id_fk", + "tableFrom": "knowledge_connector_member", + "tableTo": "credential", + "columnsFrom": ["credential_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kcm_owner_check": { + "name": "kcm_owner_check", + "value": "num_nonnulls(\"knowledge_connector_member\".\"workspace_id\", \"knowledge_connector_member\".\"organization_id\") = 1" + }, + "kcm_status_check": { + "name": "kcm_status_check", + "value": "\"knowledge_connector_member\".\"status\" IN ('active', 'suspended', 'disabled')" + }, + "kcm_subject_token_shape_check": { + "name": "kcm_subject_token_shape_check", + "value": "\"knowledge_connector_member\".\"subject_token\" ~ '^s:[^:]+:[^:]+:.+$'" + } + }, + "isRLSEnabled": false + }, + "public.knowledge_connector_member_sync_log": { + "name": "knowledge_connector_member_sync_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "members_claimed": { + "name": "members_claimed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "members_completed": { + "name": "members_completed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "members_incomplete": { + "name": "members_incomplete", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "members_failed": { + "name": "members_failed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_failed": { + "name": "docs_failed", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "processing_dispatch_failed": { + "name": "processing_dispatch_failed", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "docs_listed": { + "name": "docs_listed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_added": { + "name": "docs_added", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_updated": { + "name": "docs_updated", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_unchanged": { + "name": "docs_unchanged", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_hydrated_once": { + "name": "docs_hydrated_once", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "observations_added": { + "name": "observations_added", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "observations_removed": { + "name": "observations_removed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_tombstoned": { + "name": "docs_tombstoned", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_resurrected": { + "name": "docs_resurrected", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_purged": { + "name": "docs_purged", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "credentials_audited": { + "name": "credentials_audited", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "kcmsl_connector_started_at_idx": { + "name": "kcmsl_connector_started_at_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"started_at\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kcmsl_started_at_partial_idx": { + "name": "kcmsl_started_at_partial_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"knowledge_connector_member_sync_log\".\"status\" = 'started'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_connector_member_sync_log_connector_id_knowledge_connector_id_fk": { + "name": "knowledge_connector_member_sync_log_connector_id_knowledge_connector_id_fk", + "tableFrom": "knowledge_connector_member_sync_log", + "tableTo": "knowledge_connector", + "columnsFrom": ["connector_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kcmsl_status_check": { + "name": "kcmsl_status_check", + "value": "\"knowledge_connector_member_sync_log\".\"status\" IN ('started', 'partial', 'completed', 'failed')" + } + }, + "isRLSEnabled": false + }, + "public.knowledge_connector_partition": { + "name": "knowledge_connector_partition", + "schema": "", + "columns": { + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "partition_key": { + "name": "partition_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "generation_id": { + "name": "generation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "context": { + "name": "context", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "cursor": { + "name": "cursor", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "retry_at": { + "name": "retry_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_served_at": { + "name": "last_served_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "failure": { + "name": "failure", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "permission_cursor": { + "name": "permission_cursor", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "permission_attempts": { + "name": "permission_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "permission_retry_at": { + "name": "permission_retry_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "permission_last_served_at": { + "name": "permission_last_served_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "permission_started_at": { + "name": "permission_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "permission_failure": { + "name": "permission_failure", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "kcp_content_due_idx": { + "name": "kcp_content_due_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "generation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "retry_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_served_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kcp_permission_due_idx": { + "name": "kcp_permission_due_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "generation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_retry_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_last_served_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_connector_partition_connector_id_knowledge_connector_id_fk": { + "name": "knowledge_connector_partition_connector_id_knowledge_connector_id_fk", + "tableFrom": "knowledge_connector_partition", + "tableTo": "knowledge_connector", + "columnsFrom": ["connector_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "kcp_pk": { + "name": "kcp_pk", + "columns": ["connector_id", "partition_key"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kcp_partition_key_check": { + "name": "kcp_partition_key_check", + "value": "octet_length(\"knowledge_connector_partition\".\"partition_key\") BETWEEN 1 AND 1024" + }, + "kcp_context_check": { + "name": "kcp_context_check", + "value": "jsonb_typeof(\"knowledge_connector_partition\".\"context\") = 'object' AND octet_length(\"knowledge_connector_partition\".\"context\"::text) <= 16384" + }, + "kcp_status_check": { + "name": "kcp_status_check", + "value": "\"knowledge_connector_partition\".\"status\" IN ('pending', 'complete', 'blocked')" + }, + "kcp_cursor_check": { + "name": "kcp_cursor_check", + "value": "(\"knowledge_connector_partition\".\"cursor\" IS NULL OR octet_length(\"knowledge_connector_partition\".\"cursor\") <= 393216) AND (\"knowledge_connector_partition\".\"permission_cursor\" IS NULL OR octet_length(\"knowledge_connector_partition\".\"permission_cursor\") <= 393216)" + }, + "kcp_attempts_check": { + "name": "kcp_attempts_check", + "value": "\"knowledge_connector_partition\".\"attempts\" >= 0 AND \"knowledge_connector_partition\".\"permission_attempts\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.knowledge_connector_permission_grant": { + "name": "knowledge_connector_permission_grant", + "schema": "", + "columns": { + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "group_key": { + "name": "group_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_token": { + "name": "subject_token", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "kcpg_subject_idx": { + "name": "kcpg_subject_idx", + "columns": [ + { + "expression": "subject_token", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "group_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kcpg_snapshot_fk": { + "name": "kcpg_snapshot_fk", + "tableFrom": "knowledge_connector_permission_grant", + "tableTo": "knowledge_connector_permission_snapshot", + "columnsFrom": ["connector_id"], + "columnsTo": ["connector_id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "kcpg_pk": { + "name": "kcpg_pk", + "columns": ["connector_id", "group_key", "subject_token"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kcpg_group_check": { + "name": "kcpg_group_check", + "value": "length(\"knowledge_connector_permission_grant\".\"group_key\") BETWEEN 1 AND 255" + }, + "kcpg_subject_check": { + "name": "kcpg_subject_check", + "value": "\"knowledge_connector_permission_grant\".\"subject_token\" ~ '^u:[^[:space:]A-Z]+@[^[:space:]A-Z]+$'" + } + }, + "isRLSEnabled": false + }, + "public.knowledge_connector_permission_snapshot": { + "name": "knowledge_connector_permission_snapshot", + "schema": "", + "columns": { + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "kcps_connector_fk": { + "name": "kcps_connector_fk", + "tableFrom": "knowledge_connector_permission_snapshot", + "tableTo": "knowledge_connector", + "columnsFrom": ["connector_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kcps_revision_check": { + "name": "kcps_revision_check", + "value": "\"knowledge_connector_permission_snapshot\".\"revision\" > 0" + } + }, + "isRLSEnabled": false + }, + "public.knowledge_connector_sync_log": { + "name": "knowledge_connector_sync_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "docs_added": { + "name": "docs_added", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_updated": { + "name": "docs_updated", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_deleted": { + "name": "docs_deleted", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_unchanged": { + "name": "docs_unchanged", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_skipped": { + "name": "docs_skipped", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_failed": { + "name": "docs_failed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "listed_count": { + "name": "listed_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "kcsl_connector_started_at_idx": { + "name": "kcsl_connector_started_at_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"started_at\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kcsl_started_at_partial_idx": { + "name": "kcsl_started_at_partial_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"knowledge_connector_sync_log\".\"status\" = 'started'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_connector_sync_log_connector_id_knowledge_connector_id_fk": { + "name": "knowledge_connector_sync_log_connector_id_knowledge_connector_id_fk", + "tableFrom": "knowledge_connector_sync_log", + "tableTo": "knowledge_connector", + "columnsFrom": ["connector_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_document_observation": { + "name": "knowledge_document_observation", + "schema": "", + "columns": { + "document_id": { + "name": "document_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "member_id": { + "name": "member_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "kdo_member_idx": { + "name": "kdo_member_idx", + "columns": [ + { + "expression": "member_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_document_observation_document_id_document_id_fk": { + "name": "knowledge_document_observation_document_id_document_id_fk", + "tableFrom": "knowledge_document_observation", + "tableTo": "document", + "columnsFrom": ["document_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knowledge_document_observation_member_id_knowledge_connector_member_id_fk": { + "name": "knowledge_document_observation_member_id_knowledge_connector_member_id_fk", + "tableFrom": "knowledge_document_observation", + "tableTo": "knowledge_connector_member", + "columnsFrom": ["member_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "knowledge_document_observation_document_id_member_id_pk": { + "name": "knowledge_document_observation_document_id_member_id_pk", + "columns": ["document_id", "member_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_external_directory": { + "name": "knowledge_external_directory", + "schema": "", + "columns": { + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sync_lock_token": { + "name": "sync_lock_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sync_lock_lease_at": { + "name": "sync_lock_lease_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_started_at": { + "name": "last_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_complete_sync_at": { + "name": "last_complete_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "ked_organization_id_idx": { + "name": "ked_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ked_workspace_identity_unique": { + "name": "ked_workspace_identity_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ked_organization_identity_unique": { + "name": "ked_organization_identity_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_external_directory_workspace_id_workspace_id_fk": { + "name": "knowledge_external_directory_workspace_id_workspace_id_fk", + "tableFrom": "knowledge_external_directory", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knowledge_external_directory_organization_id_organization_id_fk": { + "name": "knowledge_external_directory_organization_id_organization_id_fk", + "tableFrom": "knowledge_external_directory", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "ked_owner_check": { + "name": "ked_owner_check", + "value": "num_nonnulls(\"knowledge_external_directory\".\"workspace_id\", \"knowledge_external_directory\".\"organization_id\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.knowledge_external_group": { + "name": "knowledge_external_group", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_group_id": { + "name": "external_group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "keg_organization_id_idx": { + "name": "keg_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "keg_organization_identity_unique": { + "name": "keg_organization_identity_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "keg_organization_synced_idx": { + "name": "keg_organization_synced_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_synced_at", + "isExpression": false, + "asc": true, + "nulls": "first" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "keg_identity_unique": { + "name": "keg_identity_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "keg_workspace_synced_idx": { + "name": "keg_workspace_synced_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_synced_at", + "isExpression": false, + "asc": true, + "nulls": "first" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_external_group_organization_id_organization_id_fk": { + "name": "knowledge_external_group_organization_id_organization_id_fk", + "tableFrom": "knowledge_external_group", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "keg_workspace_fk": { + "name": "keg_workspace_fk", + "tableFrom": "knowledge_external_group", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "keg_owner_check": { + "name": "keg_owner_check", + "value": "num_nonnulls(\"knowledge_external_group\".\"workspace_id\", \"knowledge_external_group\".\"organization_id\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.knowledge_external_group_member": { + "name": "knowledge_external_group_member", + "schema": "", + "columns": { + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_token": { + "name": "subject_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "kegm_subject_token_idx": { + "name": "kegm_subject_token_idx", + "columns": [ + { + "expression": "subject_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kegm_group_fk": { + "name": "kegm_group_fk", + "tableFrom": "knowledge_external_group_member", + "tableTo": "knowledge_external_group", + "columnsFrom": ["group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "knowledge_external_group_member_group_id_subject_token_pk": { + "name": "knowledge_external_group_member_group_id_subject_token_pk", + "columns": ["group_id", "subject_token"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_server_oauth": { + "name": "mcp_server_oauth", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "mcp_server_id": { + "name": "mcp_server_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_information": { + "name": "client_information", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tokens": { + "name": "tokens", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "code_verifier": { + "name": "code_verifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state_created_at": { + "name": "state_created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_refreshed_at": { + "name": "last_refreshed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mcp_server_oauth_server_unique": { + "name": "mcp_server_oauth_server_unique", + "columns": [ + { + "expression": "mcp_server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_server_oauth_state_idx": { + "name": "mcp_server_oauth_state_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_server_oauth_mcp_server_id_mcp_servers_id_fk": { + "name": "mcp_server_oauth_mcp_server_id_mcp_servers_id_fk", + "tableFrom": "mcp_server_oauth", + "tableTo": "mcp_servers", + "columnsFrom": ["mcp_server_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_server_oauth_user_id_user_id_fk": { + "name": "mcp_server_oauth_user_id_user_id_fk", + "tableFrom": "mcp_server_oauth", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "mcp_server_oauth_workspace_id_workspace_id_fk": { + "name": "mcp_server_oauth_workspace_id_workspace_id_fk", + "tableFrom": "mcp_server_oauth", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_server_oauth_organization_id_organization_id_fk": { + "name": "mcp_server_oauth_organization_id_organization_id_fk", + "tableFrom": "mcp_server_oauth", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "mcp_server_oauth_owner_check": { + "name": "mcp_server_oauth_owner_check", + "value": "num_nonnulls(\"mcp_server_oauth\".\"workspace_id\", \"mcp_server_oauth\".\"organization_id\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.mcp_servers": { + "name": "mcp_servers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_group_id": { + "name": "credential_group_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "managed_connector_id": { + "name": "managed_connector_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oauth_config_version": { + "name": "oauth_config_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "transport": { + "name": "transport", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'headers'" + }, + "oauth_client_id": { + "name": "oauth_client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oauth_client_secret": { + "name": "oauth_client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "headers": { + "name": "headers", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "timeout": { + "name": "timeout", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 30000 + }, + "retries": { + "name": "retries", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3 + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_connected": { + "name": "last_connected", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "connection_status": { + "name": "connection_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'disconnected'" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_config": { + "name": "status_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "tool_count": { + "name": "tool_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "last_tools_refresh": { + "name": "last_tools_refresh", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "total_requests": { + "name": "total_requests", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "last_used": { + "name": "last_used", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mcp_servers_organization_id_idx": { + "name": "mcp_servers_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_servers_workspace_enabled_idx": { + "name": "mcp_servers_workspace_enabled_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_servers_credential_group_idx": { + "name": "mcp_servers_credential_group_idx", + "columns": [ + { + "expression": "credential_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_servers_credential_group_managed_connector_unique": { + "name": "mcp_servers_credential_group_managed_connector_unique", + "columns": [ + { + "expression": "credential_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "managed_connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"mcp_servers\".\"credential_group_id\" IS NOT NULL AND \"mcp_servers\".\"managed_connector_id\" IS NOT NULL AND \"mcp_servers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_servers_workspace_deleted_partial_idx": { + "name": "mcp_servers_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"mcp_servers\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_servers_workspace_id_workspace_id_fk": { + "name": "mcp_servers_workspace_id_workspace_id_fk", + "tableFrom": "mcp_servers", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_servers_organization_id_organization_id_fk": { + "name": "mcp_servers_organization_id_organization_id_fk", + "tableFrom": "mcp_servers", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_servers_credential_group_id_credential_group_id_fk": { + "name": "mcp_servers_credential_group_id_credential_group_id_fk", + "tableFrom": "mcp_servers", + "tableTo": "credential_group", + "columnsFrom": ["credential_group_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "mcp_servers_created_by_user_id_fk": { + "name": "mcp_servers_created_by_user_id_fk", + "tableFrom": "mcp_servers", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "mcp_servers_owner_check": { + "name": "mcp_servers_owner_check", + "value": "num_nonnulls(\"mcp_servers\".\"workspace_id\", \"mcp_servers\".\"organization_id\") = 1" + }, + "mcp_servers_organization_managed_check": { + "name": "mcp_servers_organization_managed_check", + "value": "\"mcp_servers\".\"organization_id\" IS NULL OR \"mcp_servers\".\"credential_group_id\" IS NOT NULL" + }, + "mcp_servers_credential_group_managed_connector_check": { + "name": "mcp_servers_credential_group_managed_connector_check", + "value": "\"mcp_servers\".\"credential_group_id\" IS NULL OR \"mcp_servers\".\"managed_connector_id\" IS NOT NULL" + }, + "mcp_servers_managed_connector_oauth_check": { + "name": "mcp_servers_managed_connector_oauth_check", + "value": "\"mcp_servers\".\"managed_connector_id\" IS NULL OR \"mcp_servers\".\"auth_type\" = 'oauth'" + } + }, + "isRLSEnabled": false + }, + "public.member": { + "name": "member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "member_user_id_unique": { + "name": "member_user_id_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "member_organization_id_idx": { + "name": "member_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "member_user_id_user_id_fk": { + "name": "member_user_id_user_id_fk", + "tableFrom": "member", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "member_organization_id_organization_id_fk": { + "name": "member_organization_id_organization_id_fk", + "tableFrom": "member", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.memory": { + "name": "memory", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "secret_provenance_version": { + "name": "secret_provenance_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "memory_key_idx": { + "name": "memory_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workspace_idx": { + "name": "memory_workspace_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workspace_key_idx": { + "name": "memory_workspace_key_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workspace_deleted_partial_idx": { + "name": "memory_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"memory\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "memory_workspace_id_workspace_id_fk": { + "name": "memory_workspace_id_workspace_id_fk", + "tableFrom": "memory", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.memory_secret_provenance": { + "name": "memory_secret_provenance", + "schema": "", + "columns": { + "memory_id": { + "name": "memory_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entries": { + "name": "entries", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "memory_secret_provenance_memory_id_memory_id_fk": { + "name": "memory_secret_provenance_memory_id_memory_id_fk", + "tableFrom": "memory_secret_provenance", + "tableTo": "memory", + "columnsFrom": ["memory_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "memory_secret_provenance_status_check": { + "name": "memory_secret_provenance_status_check", + "value": "\"memory_secret_provenance\".\"status\" IN ('exact', 'unknown')" + } + }, + "isRLSEnabled": false + }, + "public.mothership_inbox_allowed_sender": { + "name": "mothership_inbox_allowed_sender", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "added_by": { + "name": "added_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "inbox_sender_ws_email_idx": { + "name": "inbox_sender_ws_email_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mothership_inbox_allowed_sender_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_allowed_sender_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_allowed_sender", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mothership_inbox_allowed_sender_added_by_user_id_fk": { + "name": "mothership_inbox_allowed_sender_added_by_user_id_fk", + "tableFrom": "mothership_inbox_allowed_sender", + "tableTo": "user", + "columnsFrom": ["added_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_inbox_task": { + "name": "mothership_inbox_task", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_email": { + "name": "from_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_name": { + "name": "from_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body_preview": { + "name": "body_preview", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body_text": { + "name": "body_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body_html": { + "name": "body_html", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_message_id": { + "name": "email_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "in_reply_to": { + "name": "in_reply_to", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "response_message_id": { + "name": "response_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agentmail_message_id": { + "name": "agentmail_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "trigger_job_id": { + "name": "trigger_job_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_summary": { + "name": "result_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rejection_reason": { + "name": "rejection_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "has_attachments": { + "name": "has_attachments", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cc_recipients": { + "name": "cc_recipients", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "processing_started_at": { + "name": "processing_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "inbox_task_ws_created_at_idx": { + "name": "inbox_task_ws_created_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_task_ws_status_idx": { + "name": "inbox_task_ws_status_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_task_response_msg_id_idx": { + "name": "inbox_task_response_msg_id_idx", + "columns": [ + { + "expression": "response_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_task_email_msg_id_idx": { + "name": "inbox_task_email_msg_id_idx", + "columns": [ + { + "expression": "email_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mothership_inbox_task_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_task_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_task", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mothership_inbox_task_chat_id_copilot_chats_id_fk": { + "name": "mothership_inbox_task_chat_id_copilot_chats_id_fk", + "tableFrom": "mothership_inbox_task", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_inbox_webhook": { + "name": "mothership_inbox_webhook", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "webhook_id": { + "name": "webhook_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "mothership_inbox_webhook_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_webhook_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_webhook", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mothership_inbox_webhook_workspace_id_unique": { + "name": "mothership_inbox_webhook_workspace_id_unique", + "nullsNotDistinct": false, + "columns": ["workspace_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_settings": { + "name": "mothership_settings", + "schema": "", + "columns": { + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "mcp_tool_refs": { + "name": "mcp_tool_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "custom_tool_refs": { + "name": "custom_tool_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "skill_refs": { + "name": "skill_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "mothership_settings_workspace_id_workspace_id_fk": { + "name": "mothership_settings_workspace_id_workspace_id_fk", + "tableFrom": "mothership_settings", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_access_token": { + "name": "oauth_access_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_id": { + "name": "refresh_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "resource": { + "name": "resource", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "oauth_access_token_client_id_idx": { + "name": "oauth_access_token_client_id_idx", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_access_token_session_id_idx": { + "name": "oauth_access_token_session_id_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_access_token_refresh_id_idx": { + "name": "oauth_access_token_refresh_id_idx", + "columns": [ + { + "expression": "refresh_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_access_token_user_client_idx": { + "name": "oauth_access_token_user_client_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_access_token_expires_at_idx": { + "name": "oauth_access_token_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oauth_access_token_client_id_oauth_client_client_id_fk": { + "name": "oauth_access_token_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "oauth_client", + "columnsFrom": ["client_id"], + "columnsTo": ["client_id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_access_token_session_id_session_id_fk": { + "name": "oauth_access_token_session_id_session_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "session", + "columnsFrom": ["session_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "oauth_access_token_user_id_user_id_fk": { + "name": "oauth_access_token_user_id_user_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_access_token_refresh_id_oauth_refresh_token_id_fk": { + "name": "oauth_access_token_refresh_id_oauth_refresh_token_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "oauth_refresh_token", + "columnsFrom": ["refresh_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_access_token_token_unique": { + "name": "oauth_access_token_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": { + "oauth_access_token_search_resource_check": { + "name": "oauth_access_token_search_resource_check", + "value": "NOT ('search:read' = ANY(\"oauth_access_token\".\"scopes\")) OR \"oauth_access_token\".\"resource\" IS NOT NULL" + } + }, + "isRLSEnabled": false + }, + "public.oauth_client": { + "name": "oauth_client", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "disabled": { + "name": "disabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "skip_consent": { + "name": "skip_consent", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "enable_end_session": { + "name": "enable_end_session", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "subject_type": { + "name": "subject_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uri": { + "name": "uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "contacts": { + "name": "contacts", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "tos": { + "name": "tos", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "policy": { + "name": "policy", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_id": { + "name": "software_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_version": { + "name": "software_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_statement": { + "name": "software_statement", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redirect_uris": { + "name": "redirect_uris", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "post_logout_redirect_uris": { + "name": "post_logout_redirect_uris", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "token_endpoint_auth_method": { + "name": "token_endpoint_auth_method", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "grant_types": { + "name": "grant_types", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "response_types": { + "name": "response_types", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "require_pkce": { + "name": "require_pkce", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "oauth_client_user_id_idx": { + "name": "oauth_client_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oauth_client_user_id_user_id_fk": { + "name": "oauth_client_user_id_user_id_fk", + "tableFrom": "oauth_client", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_client_client_id_unique": { + "name": "oauth_client_client_id_unique", + "nullsNotDistinct": false, + "columns": ["client_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_consent": { + "name": "oauth_consent", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "oauth_consent_client_id_idx": { + "name": "oauth_consent_client_id_idx", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oauth_consent_client_id_oauth_client_client_id_fk": { + "name": "oauth_consent_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_consent", + "tableTo": "oauth_client", + "columnsFrom": ["client_id"], + "columnsTo": ["client_id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_consent_user_id_user_id_fk": { + "name": "oauth_consent_user_id_user_id_fk", + "tableFrom": "oauth_consent", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_consent_user_client_reference_unique": { + "name": "oauth_consent_user_client_reference_unique", + "nullsNotDistinct": true, + "columns": ["user_id", "client_id", "reference_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_refresh_token": { + "name": "oauth_refresh_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "revoked": { + "name": "revoked", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "auth_time": { + "name": "auth_time", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "resource": { + "name": "resource", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "family_id": { + "name": "family_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "oauth_refresh_token_client_id_idx": { + "name": "oauth_refresh_token_client_id_idx", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_refresh_token_session_id_idx": { + "name": "oauth_refresh_token_session_id_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_refresh_token_user_client_idx": { + "name": "oauth_refresh_token_user_client_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_refresh_token_expires_at_idx": { + "name": "oauth_refresh_token_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oauth_refresh_token_client_id_oauth_client_client_id_fk": { + "name": "oauth_refresh_token_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_refresh_token", + "tableTo": "oauth_client", + "columnsFrom": ["client_id"], + "columnsTo": ["client_id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_refresh_token_session_id_session_id_fk": { + "name": "oauth_refresh_token_session_id_session_id_fk", + "tableFrom": "oauth_refresh_token", + "tableTo": "session", + "columnsFrom": ["session_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "oauth_refresh_token_user_id_user_id_fk": { + "name": "oauth_refresh_token_user_id_user_id_fk", + "tableFrom": "oauth_refresh_token", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_refresh_token_family_id_oauth_token_family_id_fk": { + "name": "oauth_refresh_token_family_id_oauth_token_family_id_fk", + "tableFrom": "oauth_refresh_token", + "tableTo": "oauth_token_family", + "columnsFrom": ["family_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_refresh_token_token_unique": { + "name": "oauth_refresh_token_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + }, + "oauth_refresh_token_family_generation_unique": { + "name": "oauth_refresh_token_family_generation_unique", + "nullsNotDistinct": false, + "columns": ["family_id", "generation"] + } + }, + "policies": {}, + "checkConstraints": { + "oauth_refresh_token_generation_check": { + "name": "oauth_refresh_token_generation_check", + "value": "\"oauth_refresh_token\".\"generation\" BETWEEN 0 AND 1000" + }, + "oauth_refresh_token_search_resource_check": { + "name": "oauth_refresh_token_search_resource_check", + "value": "NOT ('search:read' = ANY(\"oauth_refresh_token\".\"scopes\")) OR \"oauth_refresh_token\".\"resource\" IS NOT NULL" + } + }, + "isRLSEnabled": false + }, + "public.oauth_token_family": { + "name": "oauth_token_family", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "consent_id": { + "name": "consent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "current_generation": { + "name": "current_generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "oauth_token_family_client_id_idx": { + "name": "oauth_token_family_client_id_idx", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_token_family_session_id_idx": { + "name": "oauth_token_family_session_id_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_token_family_user_client_idx": { + "name": "oauth_token_family_user_client_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_token_family_consent_id_idx": { + "name": "oauth_token_family_consent_id_idx", + "columns": [ + { + "expression": "consent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_token_family_expires_at_idx": { + "name": "oauth_token_family_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oauth_token_family_client_id_oauth_client_client_id_fk": { + "name": "oauth_token_family_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_token_family", + "tableTo": "oauth_client", + "columnsFrom": ["client_id"], + "columnsTo": ["client_id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_token_family_session_id_session_id_fk": { + "name": "oauth_token_family_session_id_session_id_fk", + "tableFrom": "oauth_token_family", + "tableTo": "session", + "columnsFrom": ["session_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "oauth_token_family_user_id_user_id_fk": { + "name": "oauth_token_family_user_id_user_id_fk", + "tableFrom": "oauth_token_family", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_token_family_consent_id_oauth_consent_id_fk": { + "name": "oauth_token_family_consent_id_oauth_consent_id_fk", + "tableFrom": "oauth_token_family", + "tableTo": "oauth_consent", + "columnsFrom": ["consent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "oauth_token_family_generation_check": { + "name": "oauth_token_family_generation_check", + "value": "\"oauth_token_family\".\"current_generation\" BETWEEN 0 AND 1000" + } + }, + "isRLSEnabled": false + }, + "public.organization": { + "name": "organization", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "session_policy_settings": { + "name": "session_policy_settings", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "security_policy_version": { + "name": "security_policy_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "require_sso": { + "name": "require_sso", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "whitelabel_settings": { + "name": "whitelabel_settings", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "data_retention_settings": { + "name": "data_retention_settings", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "org_usage_limit": { + "name": "org_usage_limit", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "storage_used_bytes": { + "name": "storage_used_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "limit_notifications": { + "name": "limit_notifications", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "credit_balance": { + "name": "credit_balance", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_access_request_settings": { + "name": "organization_access_request_settings", + "schema": "", + "columns": { + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "allow_requests": { + "name": "allow_requests", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "organization_access_request_settings_organization_id_organization_id_fk": { + "name": "organization_access_request_settings_organization_id_organization_id_fk", + "tableFrom": "organization_access_request_settings", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_access_request_settings_updated_by_user_id_fk": { + "name": "organization_access_request_settings_updated_by_user_id_fk", + "tableFrom": "organization_access_request_settings", + "tableTo": "user", + "columnsFrom": ["updated_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_byok_keys": { + "name": "organization_byok_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "encrypted_api_key": { + "name": "encrypted_api_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "organization_byok_organization_provider_idx": { + "name": "organization_byok_organization_provider_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "organization_byok_keys_organization_id_organization_id_fk": { + "name": "organization_byok_keys_organization_id_organization_id_fk", + "tableFrom": "organization_byok_keys", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_byok_keys_created_by_user_id_fk": { + "name": "organization_byok_keys_created_by_user_id_fk", + "tableFrom": "organization_byok_keys", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_member_usage_limit": { + "name": "organization_member_usage_limit", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "usage_limit": { + "name": "usage_limit", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "set_by": { + "name": "set_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "org_member_usage_limit_org_user_unique": { + "name": "org_member_usage_limit_org_user_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "org_member_usage_limit_organization_id_idx": { + "name": "org_member_usage_limit_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "organization_member_usage_limit_organization_id_organization_id_fk": { + "name": "organization_member_usage_limit_organization_id_organization_id_fk", + "tableFrom": "organization_member_usage_limit", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_member_usage_limit_user_id_user_id_fk": { + "name": "organization_member_usage_limit_user_id_user_id_fk", + "tableFrom": "organization_member_usage_limit", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_member_usage_limit_set_by_user_id_fk": { + "name": "organization_member_usage_limit_set_by_user_id_fk", + "tableFrom": "organization_member_usage_limit", + "tableTo": "user", + "columnsFrom": ["set_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_search_integration": { + "name": "organization_search_integration", + "schema": "", + "columns": { + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connector_type": { + "name": "connector_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "approved": { + "name": "approved", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "organization_search_integration_organization_id_organization_id_fk": { + "name": "organization_search_integration_organization_id_organization_id_fk", + "tableFrom": "organization_search_integration", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "organization_search_integration_organization_id_connector_type_pk": { + "name": "organization_search_integration_organization_id_connector_type_pk", + "columns": ["organization_id", "connector_type"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_search_invocation": { + "name": "organization_search_invocation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "surface": { + "name": "surface", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_types": { + "name": "source_types", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "result_count": { + "name": "result_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "organization_search_invocation_org_created_idx": { + "name": "organization_search_invocation_org_created_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "organization_search_invocation_user_idx": { + "name": "organization_search_invocation_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "organization_search_invocation_organization_id_organization_id_fk": { + "name": "organization_search_invocation_organization_id_organization_id_fk", + "tableFrom": "organization_search_invocation", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_search_invocation_user_id_user_id_fk": { + "name": "organization_search_invocation_user_id_user_id_fk", + "tableFrom": "organization_search_invocation", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "organization_search_invocation_result_count_bounds": { + "name": "organization_search_invocation_result_count_bounds", + "value": "\"organization_search_invocation\".\"result_count\" BETWEEN 0 AND 100" + }, + "organization_search_invocation_source_types_bounds": { + "name": "organization_search_invocation_source_types_bounds", + "value": "cardinality(\"organization_search_invocation\".\"source_types\") <= 100" + } + }, + "isRLSEnabled": false + }, + "public.organization_search_mcp_invocation": { + "name": "organization_search_mcp_invocation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "auth_kind": { + "name": "auth_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oauth_client_id": { + "name": "oauth_client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "organization_search_mcp_invocation_org_created_idx": { + "name": "organization_search_mcp_invocation_org_created_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "organization_search_mcp_invocation_user_idx": { + "name": "organization_search_mcp_invocation_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "org_search_mcp_invocation_org_fk": { + "name": "org_search_mcp_invocation_org_fk", + "tableFrom": "organization_search_mcp_invocation", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "org_search_mcp_invocation_user_fk": { + "name": "org_search_mcp_invocation_user_fk", + "tableFrom": "organization_search_mcp_invocation", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "organization_search_mcp_invocation_tool_check": { + "name": "organization_search_mcp_invocation_tool_check", + "value": "\"organization_search_mcp_invocation\".\"tool_name\" IN ('search', 'read_document', 'chat')" + }, + "organization_search_mcp_invocation_outcome_check": { + "name": "organization_search_mcp_invocation_outcome_check", + "value": "\"organization_search_mcp_invocation\".\"outcome\" IN ('success', 'error', 'cancelled', 'rate_limited')" + }, + "organization_search_mcp_invocation_duration_check": { + "name": "organization_search_mcp_invocation_duration_check", + "value": "\"organization_search_mcp_invocation\".\"duration_ms\" >= 0" + }, + "organization_search_mcp_invocation_client_name_check": { + "name": "organization_search_mcp_invocation_client_name_check", + "value": "length(\"organization_search_mcp_invocation\".\"client_name\") <= 256" + }, + "organization_search_mcp_invocation_auth_check": { + "name": "organization_search_mcp_invocation_auth_check", + "value": "(\"organization_search_mcp_invocation\".\"auth_kind\" = 'oauth_access_token' AND \"organization_search_mcp_invocation\".\"oauth_client_id\" IS NOT NULL)\n OR (\"organization_search_mcp_invocation\".\"auth_kind\" IN ('personal_api_key', 'workspace_api_key') AND \"organization_search_mcp_invocation\".\"oauth_client_id\" IS NULL AND \"organization_search_mcp_invocation\".\"client_name\" IS NULL)" + } + }, + "isRLSEnabled": false + }, + "public.outbox_event": { + "name": "outbox_event", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "max_attempts": { + "name": "max_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10 + }, + "available_at": { + "name": "available_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "locked_at": { + "name": "locked_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "outbox_event_status_available_idx": { + "name": "outbox_event_status_available_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "available_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "outbox_event_pending_type_available_idx": { + "name": "outbox_event_pending_type_available_idx", + "columns": [ + { + "expression": "event_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "available_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"outbox_event\".\"status\" = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "outbox_event_locked_at_idx": { + "name": "outbox_event_locked_at_idx", + "columns": [ + { + "expression": "locked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "outbox_event_type_created_idx": { + "name": "outbox_event_type_created_idx", + "columns": [ + { + "expression": "event_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.paused_executions": { + "name": "paused_executions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_snapshot": { + "name": "execution_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "pause_points": { + "name": "pause_points", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "total_pause_count": { + "name": "total_pause_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "resumed_count": { + "name": "resumed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "automatic_resume_retry_count": { + "name": "automatic_resume_retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paused'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "next_resume_at": { + "name": "next_resume_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "paused_executions_workflow_id_idx": { + "name": "paused_executions_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "paused_executions_status_idx": { + "name": "paused_executions_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "paused_executions_execution_id_unique": { + "name": "paused_executions_execution_id_unique", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "paused_executions_next_resume_at_idx": { + "name": "paused_executions_next_resume_at_idx", + "columns": [ + { + "expression": "next_resume_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "status = 'paused' AND next_resume_at IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "paused_executions_workflow_id_workflow_id_fk": { + "name": "paused_executions_workflow_id_workflow_id_fk", + "tableFrom": "paused_executions", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pending_credential_draft": { + "name": "pending_credential_draft", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oauth_config": { + "name": "oauth_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pending_draft_organization_id_idx": { + "name": "pending_draft_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pending_draft_user_provider_org": { + "name": "pending_draft_user_provider_org", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pending_draft_user_provider_ws": { + "name": "pending_draft_user_provider_ws", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pending_credential_draft_user_id_user_id_fk": { + "name": "pending_credential_draft_user_id_user_id_fk", + "tableFrom": "pending_credential_draft", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pending_credential_draft_workspace_id_workspace_id_fk": { + "name": "pending_credential_draft_workspace_id_workspace_id_fk", + "tableFrom": "pending_credential_draft", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pending_credential_draft_organization_id_organization_id_fk": { + "name": "pending_credential_draft_organization_id_organization_id_fk", + "tableFrom": "pending_credential_draft", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pending_credential_draft_credential_id_credential_id_fk": { + "name": "pending_credential_draft_credential_id_credential_id_fk", + "tableFrom": "pending_credential_draft", + "tableTo": "credential", + "columnsFrom": ["credential_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pending_draft_owner_check": { + "name": "pending_draft_owner_check", + "value": "num_nonnulls(\"pending_credential_draft\".\"workspace_id\", \"pending_credential_draft\".\"organization_id\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.permission_access_request": { + "name": "permission_access_request", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requester_id": { + "name": "requester_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope_key": { + "name": "scope_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_key": { + "name": "target_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target": { + "name": "target", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "target_label": { + "name": "target_label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "membership_id": { + "name": "membership_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "group_name": { + "name": "group_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "decision_reason": { + "name": "decision_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_by": { + "name": "decided_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decision": { + "name": "decision", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "decided_at": { + "name": "decided_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "permission_access_request_pending_unique": { + "name": "permission_access_request_pending_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requester_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"permission_access_request\".\"status\" = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_access_request_org_queue_idx": { + "name": "permission_access_request_org_queue_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_access_request_requester_idx": { + "name": "permission_access_request_requester_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requester_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "permission_access_request_organization_id_organization_id_fk": { + "name": "permission_access_request_organization_id_organization_id_fk", + "tableFrom": "permission_access_request", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_access_request_requester_id_user_id_fk": { + "name": "permission_access_request_requester_id_user_id_fk", + "tableFrom": "permission_access_request", + "tableTo": "user", + "columnsFrom": ["requester_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_access_request_decided_by_user_id_fk": { + "name": "permission_access_request_decided_by_user_id_fk", + "tableFrom": "permission_access_request", + "tableTo": "user", + "columnsFrom": ["decided_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "permission_access_request_status_check": { + "name": "permission_access_request_status_check", + "value": "\"permission_access_request\".\"status\" in ('pending', 'fulfilled', 'declined', 'cancelled', 'closed')" + } + }, + "isRLSEnabled": false + }, + "public.permission_group": { + "name": "permission_group", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "membership_mode": { + "name": "membership_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'inherit'" + } + }, + "indexes": { + "permission_group_created_by_idx": { + "name": "permission_group_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_group_organization_name_unique": { + "name": "permission_group_organization_name_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_group_organization_default_unique": { + "name": "permission_group_organization_default_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "is_default = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "permission_group_organization_id_organization_id_fk": { + "name": "permission_group_organization_id_organization_id_fk", + "tableFrom": "permission_group", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_created_by_user_id_fk": { + "name": "permission_group_created_by_user_id_fk", + "tableFrom": "permission_group", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permission_group_member": { + "name": "permission_group_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "permission_group_id": { + "name": "permission_group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "assigned_by": { + "name": "assigned_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assigned_at": { + "name": "assigned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "permission_group_member_group_id_idx": { + "name": "permission_group_member_group_id_idx", + "columns": [ + { + "expression": "permission_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_group_member_group_user_unique": { + "name": "permission_group_member_group_user_unique", + "columns": [ + { + "expression": "permission_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_group_member_organization_user_idx": { + "name": "permission_group_member_organization_user_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "permission_group_member_permission_group_id_permission_group_id_fk": { + "name": "permission_group_member_permission_group_id_permission_group_id_fk", + "tableFrom": "permission_group_member", + "tableTo": "permission_group", + "columnsFrom": ["permission_group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_member_organization_id_organization_id_fk": { + "name": "permission_group_member_organization_id_organization_id_fk", + "tableFrom": "permission_group_member", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_member_user_id_user_id_fk": { + "name": "permission_group_member_user_id_user_id_fk", + "tableFrom": "permission_group_member", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_member_assigned_by_user_id_fk": { + "name": "permission_group_member_assigned_by_user_id_fk", + "tableFrom": "permission_group_member", + "tableTo": "user", + "columnsFrom": ["assigned_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permission_group_workspace": { + "name": "permission_group_workspace", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "permission_group_id": { + "name": "permission_group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "permission_group_workspace_workspace_id_idx": { + "name": "permission_group_workspace_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_group_workspace_group_workspace_unique": { + "name": "permission_group_workspace_group_workspace_unique", + "columns": [ + { + "expression": "permission_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "permission_group_workspace_permission_group_id_permission_group_id_fk": { + "name": "permission_group_workspace_permission_group_id_permission_group_id_fk", + "tableFrom": "permission_group_workspace", + "tableTo": "permission_group", + "columnsFrom": ["permission_group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_workspace_workspace_id_workspace_id_fk": { + "name": "permission_group_workspace_workspace_id_workspace_id_fk", + "tableFrom": "permission_group_workspace", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_workspace_organization_id_organization_id_fk": { + "name": "permission_group_workspace_organization_id_organization_id_fk", + "tableFrom": "permission_group_workspace", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permissions": { + "name": "permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "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 + }, + "permission_type": { + "name": "permission_type", + "type": "permission_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "permissions_user_id_idx": { + "name": "permissions_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_entity_idx": { + "name": "permissions_entity_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": {} + }, + "permissions_user_entity_type_idx": { + "name": "permissions_user_entity_type_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_user_entity_permission_idx": { + "name": "permissions_user_entity_permission_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_unique_constraint": { + "name": "permissions_unique_constraint", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "permissions_user_id_user_id_fk": { + "name": "permissions_user_id_user_id_fk", + "tableFrom": "permissions", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pinned_item": { + "name": "pinned_item", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pinned_at": { + "name": "pinned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pinned_item_user_workspace_idx": { + "name": "pinned_item_user_workspace_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pinned_item_resource_idx": { + "name": "pinned_item_resource_idx", + "columns": [ + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pinned_item_user_resource_unique": { + "name": "pinned_item_user_resource_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pinned_item_user_id_user_id_fk": { + "name": "pinned_item_user_id_user_id_fk", + "tableFrom": "pinned_item", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pinned_item_workspace_id_workspace_id_fk": { + "name": "pinned_item_workspace_id_workspace_id_fk", + "tableFrom": "pinned_item", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.public_share": { + "name": "public_share", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'public'" + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "allowed_emails": { + "name": "allowed_emails", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "public_share_token_unique": { + "name": "public_share_token_unique", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "public_share_resource_unique": { + "name": "public_share_resource_unique", + "columns": [ + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "public_share_resource_id_idx": { + "name": "public_share_resource_id_idx", + "columns": [ + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "public_share_workspace_id_idx": { + "name": "public_share_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "public_share_workspace_id_workspace_id_fk": { + "name": "public_share_workspace_id_workspace_id_fk", + "tableFrom": "public_share", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "public_share_created_by_user_id_fk": { + "name": "public_share_created_by_user_id_fk", + "tableFrom": "public_share", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.rate_limit_bucket": { + "name": "rate_limit_bucket", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tokens": { + "name": "tokens", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "last_refill_at": { + "name": "last_refill_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "blocked_until": { + "name": "blocked_until", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "capacity_state": { + "name": "capacity_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.resource_policy": { + "name": "resource_policy", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "document": { + "name": "document", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "resource_policy_organization_id_idx": { + "name": "resource_policy_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "resource_policy_resource_unique": { + "name": "resource_policy_resource_unique", + "columns": [ + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "resource_policy_workspace_id_idx": { + "name": "resource_policy_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "resource_policy_workspace_id_workspace_id_fk": { + "name": "resource_policy_workspace_id_workspace_id_fk", + "tableFrom": "resource_policy", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "resource_policy_organization_id_organization_id_fk": { + "name": "resource_policy_organization_id_organization_id_fk", + "tableFrom": "resource_policy", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "resource_policy_created_by_user_id_fk": { + "name": "resource_policy_created_by_user_id_fk", + "tableFrom": "resource_policy", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "resource_policy_updated_by_user_id_fk": { + "name": "resource_policy_updated_by_user_id_fk", + "tableFrom": "resource_policy", + "tableTo": "user", + "columnsFrom": ["updated_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "resource_policy_owner_check": { + "name": "resource_policy_owner_check", + "value": "num_nonnulls(\"resource_policy\".\"workspace_id\", \"resource_policy\".\"organization_id\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.resume_queue": { + "name": "resume_queue", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "paused_execution_id": { + "name": "paused_execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_execution_id": { + "name": "parent_execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "new_execution_id": { + "name": "new_execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "context_id": { + "name": "context_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resume_input": { + "name": "resume_input", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "queued_at": { + "name": "queued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "resume_queue_parent_status_idx": { + "name": "resume_queue_parent_status_idx", + "columns": [ + { + "expression": "parent_execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "resume_queue_new_execution_idx": { + "name": "resume_queue_new_execution_idx", + "columns": [ + { + "expression": "new_execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "resume_queue_paused_execution_id_paused_executions_id_fk": { + "name": "resume_queue_paused_execution_id_paused_executions_id_fk", + "tableFrom": "resume_queue", + "tableTo": "paused_executions", + "columnsFrom": ["paused_execution_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sandbox_image": { + "name": "sandbox_image", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "spec_hash": { + "name": "spec_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "spec": { + "name": "spec", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "sandbox_image_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "image_ref": { + "name": "image_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_image_id": { + "name": "provider_image_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "build_id": { + "name": "build_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "materialization_generation": { + "name": "materialization_generation", + "type": "bigint", + "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 + }, + "error_detail": { + "name": "error_detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "sandbox_image_provider_spec_unique": { + "name": "sandbox_image_provider_spec_unique", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "spec_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sandbox_image_status_idx": { + "name": "sandbox_image_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sandbox_image_last_used_idx": { + "name": "sandbox_image_last_used_idx", + "columns": [ + { + "expression": "last_used_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_connection": { + "name": "scim_connection", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_request_at": { + "name": "last_request_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "reconcile_lock_token": { + "name": "reconcile_lock_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reconcile_lease_at": { + "name": "reconcile_lease_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "reconciled_at": { + "name": "reconciled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "scim_connection_organization_unique": { + "name": "scim_connection_organization_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_connection_reconcile_due_idx": { + "name": "scim_connection_reconcile_due_idx", + "columns": [ + { + "expression": "reconciled_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scim_connection_organization_id_organization_id_fk": { + "name": "scim_connection_organization_id_organization_id_fk", + "tableFrom": "scim_connection", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "scim_connection_created_by_user_id_fk": { + "name": "scim_connection_created_by_user_id_fk", + "tableFrom": "scim_connection", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_credential": { + "name": "scim_credential", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_prefix": { + "name": "token_prefix", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scopes": { + "name": "scopes", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "revoked_by": { + "name": "revoked_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "scim_credential_token_hash_unique": { + "name": "scim_credential_token_hash_unique", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_credential_connection_idx": { + "name": "scim_credential_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scim_credential_connection_id_scim_connection_id_fk": { + "name": "scim_credential_connection_id_scim_connection_id_fk", + "tableFrom": "scim_credential", + "tableTo": "scim_connection", + "columnsFrom": ["connection_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "scim_credential_revoked_by_user_id_fk": { + "name": "scim_credential_revoked_by_user_id_fk", + "tableFrom": "scim_credential", + "tableTo": "user", + "columnsFrom": ["revoked_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "scim_credential_created_by_user_id_fk": { + "name": "scim_credential_created_by_user_id_fk", + "tableFrom": "scim_credential", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_group": { + "name": "scim_group", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name_key": { + "name": "display_name_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "order_key": { + "name": "order_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "scim_group_connection_display_name_unique": { + "name": "scim_group_connection_display_name_unique", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_name_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_group_connection_external_id_unique": { + "name": "scim_group_connection_external_id_unique", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "external_id is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_group_connection_order_idx": { + "name": "scim_group_connection_order_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "order_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scim_group_connection_id_scim_connection_id_fk": { + "name": "scim_group_connection_id_scim_connection_id_fk", + "tableFrom": "scim_group", + "tableTo": "scim_connection", + "columnsFrom": ["connection_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_group_mapping": { + "name": "scim_group_mapping", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_kind": { + "name": "target_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission_group_id": { + "name": "permission_group_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "permission_type": { + "name": "permission_type", + "type": "permission_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "scim_group_mapping_group_idx": { + "name": "scim_group_mapping_group_idx", + "columns": [ + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_group_mapping_permission_group_idx": { + "name": "scim_group_mapping_permission_group_idx", + "columns": [ + { + "expression": "permission_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_group_mapping_workspace_idx": { + "name": "scim_group_mapping_workspace_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_group_mapping_group_target_unique": { + "name": "scim_group_mapping_group_target_unique", + "columns": [ + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"permission_group_id\", \"workspace_id\", \"role\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scim_group_mapping_group_id_scim_group_id_fk": { + "name": "scim_group_mapping_group_id_scim_group_id_fk", + "tableFrom": "scim_group_mapping", + "tableTo": "scim_group", + "columnsFrom": ["group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "scim_group_mapping_permission_group_id_permission_group_id_fk": { + "name": "scim_group_mapping_permission_group_id_permission_group_id_fk", + "tableFrom": "scim_group_mapping", + "tableTo": "permission_group", + "columnsFrom": ["permission_group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "scim_group_mapping_workspace_id_workspace_id_fk": { + "name": "scim_group_mapping_workspace_id_workspace_id_fk", + "tableFrom": "scim_group_mapping", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "scim_group_mapping_created_by_user_id_fk": { + "name": "scim_group_mapping_created_by_user_id_fk", + "tableFrom": "scim_group_mapping", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "scim_group_mapping_target_shape": { + "name": "scim_group_mapping_target_shape", + "value": "(\n (\"scim_group_mapping\".\"target_kind\" = 'permission_group' AND \"scim_group_mapping\".\"permission_group_id\" IS NOT NULL AND \"scim_group_mapping\".\"workspace_id\" IS NULL AND \"scim_group_mapping\".\"permission_type\" IS NULL AND \"scim_group_mapping\".\"role\" IS NULL)\n OR (\"scim_group_mapping\".\"target_kind\" = 'workspace' AND \"scim_group_mapping\".\"workspace_id\" IS NOT NULL AND \"scim_group_mapping\".\"permission_type\" IS NOT NULL AND \"scim_group_mapping\".\"permission_group_id\" IS NULL AND \"scim_group_mapping\".\"role\" IS NULL)\n OR (\"scim_group_mapping\".\"target_kind\" = 'org_role' AND \"scim_group_mapping\".\"role\" IS NOT NULL AND \"scim_group_mapping\".\"permission_group_id\" IS NULL AND \"scim_group_mapping\".\"workspace_id\" IS NULL AND \"scim_group_mapping\".\"permission_type\" IS NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.scim_group_member": { + "name": "scim_group_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scim_user_id": { + "name": "scim_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "scim_group_member_group_user_unique": { + "name": "scim_group_member_group_user_unique", + "columns": [ + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scim_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_group_member_scim_user_idx": { + "name": "scim_group_member_scim_user_idx", + "columns": [ + { + "expression": "scim_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scim_group_member_group_id_scim_group_id_fk": { + "name": "scim_group_member_group_id_scim_group_id_fk", + "tableFrom": "scim_group_member", + "tableTo": "scim_group", + "columnsFrom": ["group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "scim_group_member_scim_user_id_scim_user_id_fk": { + "name": "scim_group_member_scim_user_id_scim_user_id_fk", + "tableFrom": "scim_group_member", + "tableTo": "scim_user", + "columnsFrom": ["scim_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_projection_grant": { + "name": "scim_projection_grant", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scim_user_id": { + "name": "scim_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_kind": { + "name": "target_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission_type": { + "name": "permission_type", + "type": "permission_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "baseline_permission": { + "name": "baseline_permission", + "type": "permission_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "origin": { + "name": "origin", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'directory'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "scim_projection_grant_user_target_unique": { + "name": "scim_projection_grant_user_target_unique", + "columns": [ + { + "expression": "scim_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_projection_grant_connection_idx": { + "name": "scim_projection_grant_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scim_projection_grant_connection_id_scim_connection_id_fk": { + "name": "scim_projection_grant_connection_id_scim_connection_id_fk", + "tableFrom": "scim_projection_grant", + "tableTo": "scim_connection", + "columnsFrom": ["connection_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "scim_projection_grant_scim_user_id_scim_user_id_fk": { + "name": "scim_projection_grant_scim_user_id_scim_user_id_fk", + "tableFrom": "scim_projection_grant", + "tableTo": "scim_user", + "columnsFrom": ["scim_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_request_log": { + "name": "scim_request_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "method": { + "name": "method", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "scim_type": { + "name": "scim_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "detail": { + "name": "detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "scim_request_log_connection_created_idx": { + "name": "scim_request_log_connection_created_idx", + "columns": [ + { + "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": {} + } + }, + "foreignKeys": { + "scim_request_log_connection_id_scim_connection_id_fk": { + "name": "scim_request_log_connection_id_scim_connection_id_fk", + "tableFrom": "scim_request_log", + "tableTo": "scim_connection", + "columnsFrom": ["connection_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_user": { + "name": "scim_user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_name": { + "name": "user_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "attributes": { + "name": "attributes", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "order_key": { + "name": "order_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "scim_user_connection_user_unique": { + "name": "scim_user_connection_user_unique", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_user_connection_user_name_unique": { + "name": "scim_user_connection_user_name_unique", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_user_connection_external_id_unique": { + "name": "scim_user_connection_external_id_unique", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "external_id is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_user_connection_order_idx": { + "name": "scim_user_connection_order_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "order_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_user_user_idx": { + "name": "scim_user_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scim_user_connection_id_scim_connection_id_fk": { + "name": "scim_user_connection_id_scim_connection_id_fk", + "tableFrom": "scim_user", + "tableTo": "scim_connection", + "columnsFrom": ["connection_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "scim_user_user_id_user_id_fk": { + "name": "scim_user_user_id_user_id_fk", + "tableFrom": "scim_user", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_user_tombstone": { + "name": "scim_user_tombstone", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "scim_user_tombstone_connection_external_id_unique": { + "name": "scim_user_tombstone_connection_external_id_unique", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_user_tombstone_user_idx": { + "name": "scim_user_tombstone_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scim_user_tombstone_connection_id_scim_connection_id_fk": { + "name": "scim_user_tombstone_connection_id_scim_connection_id_fk", + "tableFrom": "scim_user_tombstone", + "tableTo": "scim_connection", + "columnsFrom": ["connection_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "scim_user_tombstone_user_id_user_id_fk": { + "name": "scim_user_tombstone_user_id_user_id_fk", + "tableFrom": "scim_user_tombstone", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.secret_usage": { + "name": "secret_usage", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret_name": { + "name": "secret_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret_scope": { + "name": "secret_scope", + "type": "secret_usage_scope", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "secret_owner_user_id": { + "name": "secret_owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "source": { + "name": "source", + "type": "secret_usage_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "usage_date": { + "name": "usage_date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "use_count": { + "name": "use_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "last_execution_id": { + "name": "last_execution_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_trigger": { + "name": "last_trigger", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "secret_usage_bucket_unique": { + "name": "secret_usage_bucket_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "secret_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "secret_scope", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "secret_owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "actor_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "usage_date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secret_usage_secret_recent_idx": { + "name": "secret_usage_secret_recent_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "secret_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "secret_scope", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "secret_owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_used_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "secret_usage_workspace_id_workspace_id_fk": { + "name": "secret_usage_workspace_id_workspace_id_fk", + "tableFrom": "secret_usage", + "tableTo": "workspace", + "columnsFrom": ["workspace_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", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "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 + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "session_user_id_idx": { + "name": "session_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "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" + }, + "session_active_organization_id_organization_id_fk": { + "name": "session_active_organization_id_organization_id_fk", + "tableFrom": "session", + "tableTo": "organization", + "columnsFrom": ["active_organization_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.settings": { + "name": "settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "theme": { + "name": "theme", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "auto_connect": { + "name": "auto_connect", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "telemetry_enabled": { + "name": "telemetry_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "email_preferences": { + "name": "email_preferences", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "billing_usage_notifications_enabled": { + "name": "billing_usage_notifications_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "show_training_controls": { + "name": "show_training_controls", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "super_user_mode_enabled": { + "name": "super_user_mode_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "mothership_environment": { + "name": "mothership_environment", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "error_notifications_enabled": { + "name": "error_notifications_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "snap_to_grid_size": { + "name": "snap_to_grid_size", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "show_action_bar": { + "name": "show_action_bar", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "auto_focus_on_click": { + "name": "auto_focus_on_click", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "copilot_enabled_models": { + "name": "copilot_enabled_models", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "copilot_auto_allowed_tools": { + "name": "copilot_auto_allowed_tools", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "last_active_workspace_id": { + "name": "last_active_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "settings_user_id_user_id_fk": { + "name": "settings_user_id_user_id_fk", + "tableFrom": "settings", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "settings_user_id_unique": { + "name": "settings_user_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sim_trigger_state": { + "name": "sim_trigger_state", + "schema": "", + "columns": { + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "block_id": { + "name": "block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_key": { + "name": "scope_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "last_fired_at": { + "name": "last_fired_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "sim_trigger_state_workflow_id_workflow_id_fk": { + "name": "sim_trigger_state_workflow_id_workflow_id_fk", + "tableFrom": "sim_trigger_state", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "sim_trigger_state_workflow_id_block_id_scope_key_pk": { + "name": "sim_trigger_state_workflow_id_block_id_scope_key_pk", + "columns": ["workflow_id", "block_id", "scope_key"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.skill": { + "name": "skill", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "skill_workspace_name_unique": { + "name": "skill_workspace_name_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "skill_workspace_id_workspace_id_fk": { + "name": "skill_workspace_id_workspace_id_fk", + "tableFrom": "skill", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "skill_user_id_user_id_fk": { + "name": "skill_user_id_user_id_fk", + "tableFrom": "skill", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.skill_member": { + "name": "skill_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "skill_id": { + "name": "skill_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "invited_by": { + "name": "invited_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "skill_member_user_id_idx": { + "name": "skill_member_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "skill_member_unique": { + "name": "skill_member_unique", + "columns": [ + { + "expression": "skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "skill_member_skill_id_skill_id_fk": { + "name": "skill_member_skill_id_skill_id_fk", + "tableFrom": "skill_member", + "tableTo": "skill", + "columnsFrom": ["skill_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "skill_member_user_id_user_id_fk": { + "name": "skill_member_user_id_user_id_fk", + "tableFrom": "skill_member", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "skill_member_invited_by_user_id_fk": { + "name": "skill_member_invited_by_user_id_fk", + "tableFrom": "skill_member", + "tableTo": "user", + "columnsFrom": ["invited_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.slack_app": { + "name": "slack_app", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_client_secret": { + "name": "encrypted_client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_signing_secret": { + "name": "encrypted_signing_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revision": { + "name": "revision", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "slack_app_organization_id_organization_id_fk": { + "name": "slack_app_organization_id_organization_id_fk", + "tableFrom": "slack_app", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "slack_app_owner_check": { + "name": "slack_app_owner_check", + "value": "(\"slack_app\".\"kind\" = 'custom' AND \"slack_app\".\"organization_id\" IS NOT NULL) OR (\"slack_app\".\"kind\" = 'shared' AND \"slack_app\".\"organization_id\" IS NULL)" + }, + "slack_app_custom_credentials_check": { + "name": "slack_app_custom_credentials_check", + "value": "\"slack_app\".\"kind\" = 'shared' OR (\"slack_app\".\"client_id\" IS NOT NULL AND \"slack_app\".\"encrypted_client_secret\" IS NOT NULL AND \"slack_app\".\"encrypted_signing_secret\" IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.slack_search_installation": { + "name": "slack_search_installation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "app_id": { + "name": "app_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slack_app_id": { + "name": "slack_app_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "team_name": { + "name": "team_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bot_user_id": { + "name": "bot_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enterprise_id": { + "name": "enterprise_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "credential_version": { + "name": "credential_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_outcome": { + "name": "last_outcome", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_event_at": { + "name": "last_event_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "slack_search_installation_organization_idx": { + "name": "slack_search_installation_organization_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "slack_search_installation_credential_unique": { + "name": "slack_search_installation_credential_unique", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "slack_search_installation_app_team_unique": { + "name": "slack_search_installation_app_team_unique", + "columns": [ + { + "expression": "app_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "slack_search_installation_active_team_unique": { + "name": "slack_search_installation_active_team_unique", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"slack_search_installation\".\"enabled\" = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "slack_search_installation_organization_id_organization_id_fk": { + "name": "slack_search_installation_organization_id_organization_id_fk", + "tableFrom": "slack_search_installation", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "slack_search_installation_credential_id_credential_id_fk": { + "name": "slack_search_installation_credential_id_credential_id_fk", + "tableFrom": "slack_search_installation", + "tableTo": "credential", + "columnsFrom": ["credential_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "slack_search_installation_slack_app_id_slack_app_id_fk": { + "name": "slack_search_installation_slack_app_id_slack_app_id_fk", + "tableFrom": "slack_search_installation", + "tableTo": "slack_app", + "columnsFrom": ["slack_app_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.slack_search_turn": { + "name": "slack_search_turn", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "ordinal": { + "name": "ordinal", + "type": "integer", + "primaryKey": false, + "notNull": true, + "identity": { + "type": "always", + "name": "slack_search_turn_ordinal_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "installation_id": { + "name": "installation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "conversation_key": { + "name": "conversation_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "lease_id": { + "name": "lease_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lease_expires_at": { + "name": "lease_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "slack_search_turn_event_unique": { + "name": "slack_search_turn_event_unique", + "columns": [ + { + "expression": "installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "slack_search_turn_pending_idx": { + "name": "slack_search_turn_pending_idx", + "columns": [ + { + "expression": "installation_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": {} + }, + "slack_search_turn_thread_idx": { + "name": "slack_search_turn_thread_idx", + "columns": [ + { + "expression": "conversation_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "slack_search_turn_active_thread_unique": { + "name": "slack_search_turn_active_thread_unique", + "columns": [ + { + "expression": "conversation_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"slack_search_turn\".\"status\" = 'running'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "slack_search_turn_installation_id_slack_search_installation_id_fk": { + "name": "slack_search_turn_installation_id_slack_search_installation_id_fk", + "tableFrom": "slack_search_turn", + "tableTo": "slack_search_installation", + "columnsFrom": ["installation_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sso_domain": { + "name": "sso_domain", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "verification_token": { + "name": "verification_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "verified_at": { + "name": "verified_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "primary_provider_id": { + "name": "primary_provider_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "sso_domain_organization_id_idx": { + "name": "sso_domain_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sso_domain_domain_idx": { + "name": "sso_domain_domain_idx", + "columns": [ + { + "expression": "domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sso_domain_org_domain_unique": { + "name": "sso_domain_org_domain_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sso_domain_verified_unique": { + "name": "sso_domain_verified_unique", + "columns": [ + { + "expression": "domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "status = 'verified'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sso_domain_organization_id_organization_id_fk": { + "name": "sso_domain_organization_id_organization_id_fk", + "tableFrom": "sso_domain", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "sso_domain_created_by_user_id_fk": { + "name": "sso_domain_created_by_user_id_fk", + "tableFrom": "sso_domain", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sso_provider": { + "name": "sso_provider", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "issuer": { + "name": "issuer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oidc_config": { + "name": "oidc_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "saml_config": { + "name": "saml_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "domain_verified": { + "name": "domain_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "jit_provisioning_enabled": { + "name": "jit_provisioning_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + } + }, + "indexes": { + "sso_provider_provider_id_unique": { + "name": "sso_provider_provider_id_unique", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sso_provider_domain_idx": { + "name": "sso_provider_domain_idx", + "columns": [ + { + "expression": "domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sso_provider_user_id_idx": { + "name": "sso_provider_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sso_provider_organization_id_idx": { + "name": "sso_provider_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sso_provider_user_id_user_id_fk": { + "name": "sso_provider_user_id_user_id_fk", + "tableFrom": "sso_provider", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "sso_provider_organization_id_organization_id_fk": { + "name": "sso_provider_organization_id_organization_id_fk", + "tableFrom": "sso_provider", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.subscription": { + "name": "subscription", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "plan": { + "name": "plan", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_subscription_id": { + "name": "stripe_subscription_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "period_start": { + "name": "period_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "period_end": { + "name": "period_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "cancel_at_period_end": { + "name": "cancel_at_period_end", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "cancel_at": { + "name": "cancel_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "canceled_at": { + "name": "canceled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "seats": { + "name": "seats", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "trial_start": { + "name": "trial_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "trial_end": { + "name": "trial_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "billing_interval": { + "name": "billing_interval", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_schedule_id": { + "name": "stripe_schedule_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "last_closed_period_start": { + "name": "last_closed_period_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "subscription_reference_status_idx": { + "name": "subscription_reference_status_idx", + "columns": [ + { + "expression": "reference_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "subscription_cycle_close_lagging_idx": { + "name": "subscription_cycle_close_lagging_idx", + "columns": [ + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"subscription\".\"status\" in ('active', 'past_due') and \"subscription\".\"period_start\" is not null and (\"subscription\".\"last_closed_period_start\" is null or \"subscription\".\"last_closed_period_start\" < \"subscription\".\"period_start\")", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "check_enterprise_metadata": { + "name": "check_enterprise_metadata", + "value": "plan != 'enterprise' OR metadata IS NOT NULL" + } + }, + "isRLSEnabled": false + }, + "public.table_jobs": { + "name": "table_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "rows_processed": { + "name": "rows_processed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "table_jobs_one_active_per_table": { + "name": "table_jobs_one_active_per_table", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"table_jobs\".\"status\" = 'running' AND \"table_jobs\".\"type\" <> 'export'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_jobs_watchdog_idx": { + "name": "table_jobs_watchdog_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_jobs_table_started_idx": { + "name": "table_jobs_table_started_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "table_jobs_table_id_user_table_definitions_id_fk": { + "name": "table_jobs_table_id_user_table_definitions_id_fk", + "tableFrom": "table_jobs", + "tableTo": "user_table_definitions", + "columnsFrom": ["table_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "table_jobs_workspace_id_workspace_id_fk": { + "name": "table_jobs_workspace_id_workspace_id_fk", + "tableFrom": "table_jobs", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.table_row_executions": { + "name": "table_row_executions", + "schema": "", + "columns": { + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "row_id": { + "name": "row_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "job_id": { + "name": "job_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "running_block_ids": { + "name": "running_block_ids", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "block_errors": { + "name": "block_errors", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "capability_governed_user_id": { + "name": "capability_governed_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enrichment_details": { + "name": "enrichment_details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "table_row_executions_table_status_idx": { + "name": "table_row_executions_table_status_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"table_row_executions\".\"status\" IN ('queued', 'running', 'pending')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_row_executions_execution_id_idx": { + "name": "table_row_executions_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"table_row_executions\".\"execution_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_row_executions_table_group_idx": { + "name": "table_row_executions_table_group_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "table_row_executions_table_id_user_table_definitions_id_fk": { + "name": "table_row_executions_table_id_user_table_definitions_id_fk", + "tableFrom": "table_row_executions", + "tableTo": "user_table_definitions", + "columnsFrom": ["table_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "table_row_executions_row_id_user_table_rows_id_fk": { + "name": "table_row_executions_row_id_user_table_rows_id_fk", + "tableFrom": "table_row_executions", + "tableTo": "user_table_rows", + "columnsFrom": ["row_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "table_row_executions_capability_governed_user_id_user_id_fk": { + "name": "table_row_executions_capability_governed_user_id_user_id_fk", + "tableFrom": "table_row_executions", + "tableTo": "user", + "columnsFrom": ["capability_governed_user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "table_row_executions_row_id_group_id_pk": { + "name": "table_row_executions_row_id_group_id_pk", + "columns": ["row_id", "group_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.table_run_dispatches": { + "name": "table_run_dispatches", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "cursor": { + "name": "cursor", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "limit": { + "name": "limit", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "processed_count": { + "name": "processed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_manual_run": { + "name": "is_manual_run", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "triggered_by_user_id": { + "name": "triggered_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "capability_governed_user_id": { + "name": "capability_governed_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_at": { + "name": "requested_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "heartbeat_at": { + "name": "heartbeat_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "table_run_dispatches_active_idx": { + "name": "table_run_dispatches_active_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_run_dispatches_watchdog_idx": { + "name": "table_run_dispatches_watchdog_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_run_dispatches_governed_active_idx": { + "name": "table_run_dispatches_governed_active_idx", + "columns": [ + { + "expression": "capability_governed_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"table_run_dispatches\".\"status\" IN ('pending', 'dispatching')", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "table_run_dispatches_table_id_user_table_definitions_id_fk": { + "name": "table_run_dispatches_table_id_user_table_definitions_id_fk", + "tableFrom": "table_run_dispatches", + "tableTo": "user_table_definitions", + "columnsFrom": ["table_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "table_run_dispatches_workspace_id_workspace_id_fk": { + "name": "table_run_dispatches_workspace_id_workspace_id_fk", + "tableFrom": "table_run_dispatches", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "table_run_dispatches_triggered_by_user_id_user_id_fk": { + "name": "table_run_dispatches_triggered_by_user_id_user_id_fk", + "tableFrom": "table_run_dispatches", + "tableTo": "user", + "columnsFrom": ["triggered_by_user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "table_run_dispatches_capability_governed_user_id_user_id_fk": { + "name": "table_run_dispatches_capability_governed_user_id_user_id_fk", + "tableFrom": "table_run_dispatches", + "tableTo": "user", + "columnsFrom": ["capability_governed_user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.table_views": { + "name": "table_views", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "table_views_table_created_idx": { + "name": "table_views_table_created_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_views_workspace_created_idx": { + "name": "table_views_workspace_created_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_views_table_default_unique": { + "name": "table_views_table_default_unique", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "is_default = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "table_views_table_id_user_table_definitions_id_fk": { + "name": "table_views_table_id_user_table_definitions_id_fk", + "tableFrom": "table_views", + "tableTo": "user_table_definitions", + "columnsFrom": ["table_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "table_views_workspace_id_workspace_id_fk": { + "name": "table_views_workspace_id_workspace_id_fk", + "tableFrom": "table_views", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "table_views_created_by_user_id_fk": { + "name": "table_views_created_by_user_id_fk", + "tableFrom": "table_views", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.upload_session": { + "name": "upload_session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "purpose": { + "name": "purpose", + "type": "upload_session_purpose", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "method": { + "name": "method", + "type": "upload_session_method", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "storage_context": { + "name": "storage_context", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "final_key": { + "name": "final_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "storage_provider": { + "name": "storage_provider", + "type": "upload_session_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "provider_upload_id": { + "name": "provider_upload_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_object_version": { + "name": "provider_object_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "file_name": { + "name": "file_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_size": { + "name": "file_size", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "part_size": { + "name": "part_size", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "part_count": { + "name": "part_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "upload_session_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'uploading'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "processing_lease_id": { + "name": "processing_lease_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "processing_lease_expires_at": { + "name": "processing_lease_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_file_id": { + "name": "completed_file_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "upload_session_token_hash_unique": { + "name": "upload_session_token_hash_unique", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "upload_session_final_key_unique": { + "name": "upload_session_final_key_unique", + "columns": [ + { + "expression": "final_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "upload_session_status_expires_at_idx": { + "name": "upload_session_status_expires_at_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": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.usage_log": { + "name": "usage_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "category": { + "name": "category", + "type": "usage_log_category", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "usage_log_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "cost": { + "name": "cost", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "event_key": { + "name": "event_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "billing_entity_type": { + "name": "billing_entity_type", + "type": "billing_entity_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "billing_entity_id": { + "name": "billing_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "billing_period_start": { + "name": "billing_period_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "billing_period_end": { + "name": "billing_period_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "usage_log_user_created_at_idx": { + "name": "usage_log_user_created_at_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_source_idx": { + "name": "usage_log_source_idx", + "columns": [ + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_workspace_id_idx": { + "name": "usage_log_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_workflow_id_idx": { + "name": "usage_log_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_event_key_unique": { + "name": "usage_log_event_key_unique", + "columns": [ + { + "expression": "event_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"usage_log\".\"event_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_billing_entity_period_idx": { + "name": "usage_log_billing_entity_period_idx", + "columns": [ + { + "expression": "billing_entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_start", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_end", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_log\".\"billing_entity_type\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_billing_period_cost_idx": { + "name": "usage_log_billing_period_cost_idx", + "columns": [ + { + "expression": "billing_entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_start", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_end", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_log\".\"billing_entity_type\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_billing_entity_created_at_cost_idx": { + "name": "usage_log_billing_entity_created_at_cost_idx", + "columns": [ + { + "expression": "billing_entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_log\".\"billing_entity_type\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_workspace_created_at_idx": { + "name": "usage_log_workspace_created_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_execution_id_idx": { + "name": "usage_log_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "usage_log_user_id_user_id_fk": { + "name": "usage_log_user_id_user_id_fk", + "tableFrom": "usage_log", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "usage_log_workspace_id_workspace_id_fk": { + "name": "usage_log_workspace_id_workspace_id_fk", + "tableFrom": "usage_log", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "usage_log_workflow_id_workflow_id_fk": { + "name": "usage_log_workflow_id_workflow_id_fk", + "tableFrom": "usage_log", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "usage_log_billing_scope_all_or_none": { + "name": "usage_log_billing_scope_all_or_none", + "value": "(\n (\"usage_log\".\"billing_entity_type\" IS NULL AND \"usage_log\".\"billing_entity_id\" IS NULL AND \"usage_log\".\"billing_period_start\" IS NULL AND \"usage_log\".\"billing_period_end\" IS NULL)\n OR\n (\"usage_log\".\"billing_entity_type\" IS NOT NULL AND \"usage_log\".\"billing_entity_id\" IS NOT NULL AND \"usage_log\".\"billing_period_start\" IS NOT NULL AND \"usage_log\".\"billing_period_end\" IS NOT NULL AND \"usage_log\".\"billing_period_start\" < \"usage_log\".\"billing_period_end\")\n )" + } + }, + "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 + }, + "normalized_email": { + "name": "normalized_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "suspended_at": { + "name": "suspended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "suspension_source": { + "name": "suspension_source", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_email_lower_idx": { + "name": "user_email_lower_idx", + "columns": [ + { + "expression": "lower(btrim(\"email\"))", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + }, + "user_normalized_email_unique": { + "name": "user_normalized_email_unique", + "nullsNotDistinct": false, + "columns": ["normalized_email"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_stats": { + "name": "user_stats", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "current_usage_limit": { + "name": "current_usage_limit", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'5'" + }, + "usage_limit_updated_at": { + "name": "usage_limit_updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "last_period_cost": { + "name": "last_period_cost", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "billed_overage_this_period": { + "name": "billed_overage_this_period", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "credit_balance": { + "name": "credit_balance", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "last_period_copilot_cost": { + "name": "last_period_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "storage_used_bytes": { + "name": "storage_used_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "billing_blocked": { + "name": "billing_blocked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "billing_blocked_reason": { + "name": "billing_blocked_reason", + "type": "billing_blocked_reason", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "limit_notifications": { + "name": "limit_notifications", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + } + }, + "indexes": {}, + "foreignKeys": { + "user_stats_user_id_user_id_fk": { + "name": "user_stats_user_id_user_id_fk", + "tableFrom": "user_stats", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_stats_user_id_unique": { + "name": "user_stats_user_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_table_definitions": { + "name": "user_table_definitions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "folder_id": { + "name": "folder_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "schema": { + "name": "schema", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "max_rows": { + "name": "max_rows", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10000 + }, + "row_count": { + "name": "row_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "rows_version": { + "name": "rows_version", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "schema_locked": { + "name": "schema_locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "insert_locked": { + "name": "insert_locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "update_locked": { + "name": "update_locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "delete_locked": { + "name": "delete_locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_table_def_workspace_id_idx": { + "name": "user_table_def_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_def_folder_id_idx": { + "name": "user_table_def_folder_id_idx", + "columns": [ + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_def_workspace_name_unique": { + "name": "user_table_def_workspace_name_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"user_table_definitions\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_def_archived_at_idx": { + "name": "user_table_def_archived_at_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_def_workspace_archived_partial_idx": { + "name": "user_table_def_workspace_archived_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"user_table_definitions\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_table_definitions_workspace_id_workspace_id_fk": { + "name": "user_table_definitions_workspace_id_workspace_id_fk", + "tableFrom": "user_table_definitions", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_table_definitions_folder_id_folder_id_fk": { + "name": "user_table_definitions_folder_id_folder_id_fk", + "tableFrom": "user_table_definitions", + "tableTo": "folder", + "columnsFrom": ["folder_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "user_table_definitions_created_by_user_id_fk": { + "name": "user_table_definitions_created_by_user_id_fk", + "tableFrom": "user_table_definitions", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_table_row_secret_provenance": { + "name": "user_table_row_secret_provenance", + "schema": "", + "columns": { + "row_id": { + "name": "row_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "content_updated_at": { + "name": "content_updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entries": { + "name": "entries", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_table_row_secret_provenance_row_id_user_table_rows_id_fk": { + "name": "user_table_row_secret_provenance_row_id_user_table_rows_id_fk", + "tableFrom": "user_table_row_secret_provenance", + "tableTo": "user_table_rows", + "columnsFrom": ["row_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "user_table_row_secret_provenance_status_check": { + "name": "user_table_row_secret_provenance_status_check", + "value": "\"user_table_row_secret_provenance\".\"status\" IN ('exact', 'unknown')" + } + }, + "isRLSEnabled": false + }, + "public.user_table_rows": { + "name": "user_table_rows", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "order_key": { + "name": "order_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_provenance_version": { + "name": "secret_provenance_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_table_rows_tenant_data_gin_idx": { + "name": "user_table_rows_tenant_data_gin_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"data\" jsonb_path_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "user_table_rows_workspace_table_idx": { + "name": "user_table_rows_workspace_table_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_rows_table_position_idx": { + "name": "user_table_rows_table_position_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_rows_table_order_key_idx": { + "name": "user_table_rows_table_order_key_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "order_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_rows_table_created_id_idx": { + "name": "user_table_rows_table_created_id_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_rows_table_id_id_idx": { + "name": "user_table_rows_table_id_id_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_table_rows_table_id_user_table_definitions_id_fk": { + "name": "user_table_rows_table_id_user_table_definitions_id_fk", + "tableFrom": "user_table_rows", + "tableTo": "user_table_definitions", + "columnsFrom": ["table_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_table_rows_workspace_id_workspace_id_fk": { + "name": "user_table_rows_workspace_id_workspace_id_fk", + "tableFrom": "user_table_rows", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_table_rows_created_by_user_id_fk": { + "name": "user_table_rows_created_by_user_id_fk", + "tableFrom": "user_table_rows", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "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", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "verification_identifier_idx": { + "name": "verification_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "verification_expires_at_idx": { + "name": "verification_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.waitlist": { + "name": "waitlist", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "waitlist_email_unique": { + "name": "waitlist_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.webhook": { + "name": "webhook", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deployment_version_id": { + "name": "deployment_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "registration_status": { + "name": "registration_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "registration_generation": { + "name": "registration_generation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "config_fingerprint": { + "name": "config_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prepared_at": { + "name": "prepared_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "block_id": { + "name": "block_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "routing_key": { + "name": "routing_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_config": { + "name": "provider_config", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "failed_count": { + "name": "failed_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "last_failed_at": { + "name": "last_failed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "path_deployment_unique": { + "name": "path_deployment_unique", + "columns": [ + { + "expression": "path", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"webhook\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_workflow_deployment_idx": { + "name": "webhook_workflow_deployment_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_routing_key_active_idx": { + "name": "webhook_routing_key_active_idx", + "columns": [ + { + "expression": "routing_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"webhook\".\"archived_at\" IS NULL AND \"webhook\".\"routing_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_archived_at_partial_idx": { + "name": "webhook_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"webhook\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_webhook_on_provider_is_active_workflow_id_deploym_bdeed5468": { + "name": "idx_webhook_on_provider_is_active_workflow_id_deploym_bdeed5468", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_webhook_on_workflow_id_block_id_updated_at_desc": { + "name": "idx_webhook_on_workflow_id_block_id_updated_at_desc", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_active_registration_unique": { + "name": "webhook_active_registration_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"webhook\".\"registration_status\" = 'active' AND \"webhook\".\"block_id\" IS NOT NULL AND \"webhook\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_candidate_registration_unique": { + "name": "webhook_candidate_registration_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"webhook\".\"registration_status\" = 'candidate' AND \"webhook\".\"block_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_registration_status_generation_idx": { + "name": "webhook_registration_status_generation_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "registration_status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "registration_generation", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "webhook_workflow_id_workflow_id_fk": { + "name": "webhook_workflow_id_workflow_id_fk", + "tableFrom": "webhook", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "webhook_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "webhook_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "webhook", + "tableTo": "workflow_deployment_version", + "columnsFrom": ["deployment_version_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "webhook_registration_status_check": { + "name": "webhook_registration_status_check", + "value": "\"webhook\".\"registration_status\" IS NULL OR \"webhook\".\"registration_status\" IN ('active', 'candidate', 'retired', 'orphaned')" + }, + "webhook_registration_generation_check": { + "name": "webhook_registration_generation_check", + "value": "\"webhook\".\"registration_generation\" IS NULL OR \"webhook\".\"registration_generation\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.webhook_path_claim": { + "name": "webhook_path_claim", + "schema": "", + "columns": { + "path": { + "name": "path", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "webhook_path_claim_workflow_idx": { + "name": "webhook_path_claim_workflow_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "webhook_path_claim_workflow_id_workflow_id_fk": { + "name": "webhook_path_claim_workflow_id_workflow_id_fk", + "tableFrom": "webhook_path_claim", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "webhook_path_claim_generation_check": { + "name": "webhook_path_claim_generation_check", + "value": "\"webhook_path_claim\".\"generation\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.workflow": { + "name": "workflow", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "folder_id": { + "name": "folder_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_synced": { + "name": "last_synced", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "is_deployed": { + "name": "is_deployed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "deployed_at": { + "name": "deployed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "is_public_api": { + "name": "is_public_api", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "locked": { + "name": "locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "fork_sync_excluded": { + "name": "fork_sync_excluded", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "run_count": { + "name": "run_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "variables": { + "name": "variables", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workflow_user_id_idx": { + "name": "workflow_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_workspace_id_idx": { + "name": "workflow_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_user_workspace_idx": { + "name": "workflow_user_workspace_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_workspace_folder_name_active_unique": { + "name": "workflow_workspace_folder_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"folder_id\", '')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_folder_sort_idx": { + "name": "workflow_folder_sort_idx", + "columns": [ + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_active_workspace_sort_idx": { + "name": "workflow_active_workspace_sort_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_archived_at_idx": { + "name": "workflow_archived_at_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_workspace_archived_partial_idx": { + "name": "workflow_workspace_archived_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_user_id_user_id_fk": { + "name": "workflow_user_id_user_id_fk", + "tableFrom": "workflow", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_workspace_id_workspace_id_fk": { + "name": "workflow_workspace_id_workspace_id_fk", + "tableFrom": "workflow", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_folder_id_folder_id_fk": { + "name": "workflow_folder_id_folder_id_fk", + "tableFrom": "workflow", + "tableTo": "folder", + "columnsFrom": ["folder_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_blocks": { + "name": "workflow_blocks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "position_x": { + "name": "position_x", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "position_y": { + "name": "position_y", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "horizontal_handles": { + "name": "horizontal_handles", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_wide": { + "name": "is_wide", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "advanced_mode": { + "name": "advanced_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "trigger_mode": { + "name": "trigger_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "error_enabled": { + "name": "error_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "retry": { + "name": "retry", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "locked": { + "name": "locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "height": { + "name": "height", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "sub_blocks": { + "name": "sub_blocks", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "outputs": { + "name": "outputs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_blocks_workflow_id_idx": { + "name": "workflow_blocks_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_blocks_type_idx": { + "name": "workflow_blocks_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_blocks_workflow_id_workflow_id_fk": { + "name": "workflow_blocks_workflow_id_workflow_id_fk", + "tableFrom": "workflow_blocks", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_checkpoints": { + "name": "workflow_checkpoints", + "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 + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_state": { + "name": "workflow_state", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_checkpoints_user_id_idx": { + "name": "workflow_checkpoints_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_workflow_id_idx": { + "name": "workflow_checkpoints_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_chat_id_idx": { + "name": "workflow_checkpoints_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_message_id_idx": { + "name": "workflow_checkpoints_message_id_idx", + "columns": [ + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_user_workflow_idx": { + "name": "workflow_checkpoints_user_workflow_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_workflow_chat_idx": { + "name": "workflow_checkpoints_workflow_chat_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_created_at_idx": { + "name": "workflow_checkpoints_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_chat_created_at_idx": { + "name": "workflow_checkpoints_chat_created_at_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_checkpoints_user_id_user_id_fk": { + "name": "workflow_checkpoints_user_id_user_id_fk", + "tableFrom": "workflow_checkpoints", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_checkpoints_workflow_id_workflow_id_fk": { + "name": "workflow_checkpoints_workflow_id_workflow_id_fk", + "tableFrom": "workflow_checkpoints", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_checkpoints_chat_id_copilot_chats_id_fk": { + "name": "workflow_checkpoints_chat_id_copilot_chats_id_fk", + "tableFrom": "workflow_checkpoints", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_deployment_operation": { + "name": "workflow_deployment_operation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deployment_version_id": { + "name": "deployment_version_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "previous_active_version_id": { + "name": "previous_active_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "protocol_version": { + "name": "protocol_version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'preparing'" + }, + "component_readiness": { + "name": "component_readiness", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_hash": { + "name": "request_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_deployment_operation_workflow_generation_unique": { + "name": "workflow_deployment_operation_workflow_generation_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "generation", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_deployment_operation_workflow_idempotency_unique": { + "name": "workflow_deployment_operation_workflow_idempotency_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow_deployment_operation\".\"idempotency_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_deployment_operation_workflow_in_flight_unique": { + "name": "workflow_deployment_operation_workflow_in_flight_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow_deployment_operation\".\"status\" IN ('preparing', 'activating')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_deployment_operation_workflow_status_idx": { + "name": "workflow_deployment_operation_workflow_status_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_deployment_operation_deployment_version_idx": { + "name": "workflow_deployment_operation_deployment_version_idx", + "columns": [ + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_deployment_operation_workflow_version_generation_idx": { + "name": "workflow_deployment_operation_workflow_version_generation_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "generation", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_deployment_operation_workflow_id_workflow_id_fk": { + "name": "workflow_deployment_operation_workflow_id_workflow_id_fk", + "tableFrom": "workflow_deployment_operation", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_deployment_operation_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "workflow_deployment_operation_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "workflow_deployment_operation", + "tableTo": "workflow_deployment_version", + "columnsFrom": ["deployment_version_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_deployment_operation_previous_active_version_id_workflow_deployment_version_id_fk": { + "name": "workflow_deployment_operation_previous_active_version_id_workflow_deployment_version_id_fk", + "tableFrom": "workflow_deployment_operation", + "tableTo": "workflow_deployment_version", + "columnsFrom": ["previous_active_version_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "workflow_deployment_operation_action_check": { + "name": "workflow_deployment_operation_action_check", + "value": "\"workflow_deployment_operation\".\"action\" IN ('deploy', 'activate')" + }, + "workflow_deployment_operation_status_check": { + "name": "workflow_deployment_operation_status_check", + "value": "\"workflow_deployment_operation\".\"status\" IN ('preparing', 'activating', 'active', 'failed', 'superseded')" + }, + "workflow_deployment_operation_generation_check": { + "name": "workflow_deployment_operation_generation_check", + "value": "\"workflow_deployment_operation\".\"generation\" > 0" + }, + "workflow_deployment_operation_protocol_version_check": { + "name": "workflow_deployment_operation_protocol_version_check", + "value": "\"workflow_deployment_operation\".\"protocol_version\" > 0" + } + }, + "isRLSEnabled": false + }, + "public.workflow_deployment_version": { + "name": "workflow_deployment_version", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workflow_deployment_version_workflow_version_unique": { + "name": "workflow_deployment_version_workflow_version_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_deployment_version_workflow_active_idx": { + "name": "workflow_deployment_version_workflow_active_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_deployment_version_created_at_idx": { + "name": "workflow_deployment_version_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_deployment_version_workflow_id_workflow_id_fk": { + "name": "workflow_deployment_version_workflow_id_workflow_id_fk", + "tableFrom": "workflow_deployment_version", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_edges": { + "name": "workflow_edges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_block_id": { + "name": "source_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_block_id": { + "name": "target_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_handle": { + "name": "source_handle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_handle": { + "name": "target_handle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_edges_workflow_id_idx": { + "name": "workflow_edges_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_edges_workflow_source_idx": { + "name": "workflow_edges_workflow_source_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_edges_workflow_target_idx": { + "name": "workflow_edges_workflow_target_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_edges_workflow_id_workflow_id_fk": { + "name": "workflow_edges_workflow_id_workflow_id_fk", + "tableFrom": "workflow_edges", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_edges_source_block_id_workflow_blocks_id_fk": { + "name": "workflow_edges_source_block_id_workflow_blocks_id_fk", + "tableFrom": "workflow_edges", + "tableTo": "workflow_blocks", + "columnsFrom": ["source_block_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_edges_target_block_id_workflow_blocks_id_fk": { + "name": "workflow_edges_target_block_id_workflow_blocks_id_fk", + "tableFrom": "workflow_edges", + "tableTo": "workflow_blocks", + "columnsFrom": ["target_block_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_execution_logs": { + "name": "workflow_execution_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state_snapshot_id": { + "name": "state_snapshot_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deployment_version_id": { + "name": "deployment_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "execution_deadline_at": { + "name": "execution_deadline_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "total_duration_ms": { + "name": "total_duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "execution_data": { + "name": "execution_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "cost_total": { + "name": "cost_total", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "models_used": { + "name": "models_used", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "files": { + "name": "files", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_execution_logs_workflow_id_idx": { + "name": "workflow_execution_logs_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_state_snapshot_id_idx": { + "name": "workflow_execution_logs_state_snapshot_id_idx", + "columns": [ + { + "expression": "state_snapshot_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_deployment_version_id_idx": { + "name": "workflow_execution_logs_deployment_version_id_idx", + "columns": [ + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_trigger_idx": { + "name": "workflow_execution_logs_trigger_idx", + "columns": [ + { + "expression": "trigger", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_level_idx": { + "name": "workflow_execution_logs_level_idx", + "columns": [ + { + "expression": "level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_started_at_idx": { + "name": "workflow_execution_logs_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_execution_id_unique": { + "name": "workflow_execution_logs_execution_id_unique", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_workflow_started_at_idx": { + "name": "workflow_execution_logs_workflow_started_at_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_workspace_started_at_idx": { + "name": "workflow_execution_logs_workspace_started_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_workspace_activity_idx": { + "name": "workflow_execution_logs_workspace_activity_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "total_duration_ms", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "trigger", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": true, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_workspace_started_at_id_desc_idx": { + "name": "workflow_execution_logs_workspace_started_at_id_desc_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"started_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"id\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_workspace_cost_total_idx": { + "name": "workflow_execution_logs_workspace_cost_total_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_total", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_models_used_idx": { + "name": "workflow_execution_logs_models_used_idx", + "columns": [ + { + "expression": "models_used", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "workflow_execution_logs_workspace_ended_at_id_idx": { + "name": "workflow_execution_logs_workspace_ended_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"ended_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_running_started_at_idx": { + "name": "workflow_execution_logs_running_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "status = 'running'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_running_deadline_idx": { + "name": "workflow_execution_logs_running_deadline_idx", + "columns": [ + { + "expression": "execution_deadline_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_execution_logs\".\"status\" = 'running' AND \"workflow_execution_logs\".\"execution_deadline_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_redacting_started_at_idx": { + "name": "workflow_execution_logs_redacting_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "status = 'redacting'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_redacting_deadline_idx": { + "name": "workflow_execution_logs_redacting_deadline_idx", + "columns": [ + { + "expression": "execution_deadline_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_execution_logs\".\"status\" = 'redacting' AND \"workflow_execution_logs\".\"execution_deadline_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_completed_ended_at_idx": { + "name": "workflow_execution_logs_completed_ended_at_idx", + "columns": [ + { + "expression": "ended_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_execution_logs\".\"status\" = 'completed' AND \"workflow_execution_logs\".\"level\" = 'info' AND \"workflow_execution_logs\".\"ended_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_execution_logs_workflow_id_workflow_id_fk": { + "name": "workflow_execution_logs_workflow_id_workflow_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workflow_execution_logs_workspace_id_workspace_id_fk": { + "name": "workflow_execution_logs_workspace_id_workspace_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_execution_logs_state_snapshot_id_workflow_execution_snapshots_id_fk": { + "name": "workflow_execution_logs_state_snapshot_id_workflow_execution_snapshots_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow_execution_snapshots", + "columnsFrom": ["state_snapshot_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workflow_execution_logs_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "workflow_execution_logs_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow_deployment_version", + "columnsFrom": ["deployment_version_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_execution_snapshots": { + "name": "workflow_execution_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state_hash": { + "name": "state_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state_data": { + "name": "state_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_snapshots_workflow_id_idx": { + "name": "workflow_snapshots_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_hash_idx": { + "name": "workflow_snapshots_hash_idx", + "columns": [ + { + "expression": "state_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_workflow_hash_idx": { + "name": "workflow_snapshots_workflow_hash_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "state_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_created_at_idx": { + "name": "workflow_snapshots_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_execution_snapshots_workflow_id_workflow_id_fk": { + "name": "workflow_execution_snapshots_workflow_id_workflow_id_fk", + "tableFrom": "workflow_execution_snapshots", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_mcp_server": { + "name": "workflow_mcp_server", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_mcp_server_workspace_id_idx": { + "name": "workflow_mcp_server_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_created_by_idx": { + "name": "workflow_mcp_server_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_deleted_at_idx": { + "name": "workflow_mcp_server_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_workspace_deleted_partial_idx": { + "name": "workflow_mcp_server_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_mcp_server\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_mcp_server_workspace_id_workspace_id_fk": { + "name": "workflow_mcp_server_workspace_id_workspace_id_fk", + "tableFrom": "workflow_mcp_server", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_mcp_server_created_by_user_id_fk": { + "name": "workflow_mcp_server_created_by_user_id_fk", + "tableFrom": "workflow_mcp_server", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_mcp_tool": { + "name": "workflow_mcp_tool", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "server_id": { + "name": "server_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_description": { + "name": "tool_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "parameter_schema": { + "name": "parameter_schema", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "parameter_description_overrides": { + "name": "parameter_description_overrides", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'::json" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_mcp_tool_server_id_idx": { + "name": "workflow_mcp_tool_server_id_idx", + "columns": [ + { + "expression": "server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_workflow_id_idx": { + "name": "workflow_mcp_tool_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_server_workflow_unique": { + "name": "workflow_mcp_tool_server_workflow_unique", + "columns": [ + { + "expression": "server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow_mcp_tool\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_archived_at_partial_idx": { + "name": "workflow_mcp_tool_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_mcp_tool\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_mcp_tool_server_id_workflow_mcp_server_id_fk": { + "name": "workflow_mcp_tool_server_id_workflow_mcp_server_id_fk", + "tableFrom": "workflow_mcp_tool", + "tableTo": "workflow_mcp_server", + "columnsFrom": ["server_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_mcp_tool_workflow_id_workflow_id_fk": { + "name": "workflow_mcp_tool_workflow_id_workflow_id_fk", + "tableFrom": "workflow_mcp_tool", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_schedule": { + "name": "workflow_schedule", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deployment_version_id": { + "name": "deployment_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deployment_operation_id": { + "name": "deployment_operation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "block_id": { + "name": "block_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_ran_at": { + "name": "last_ran_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_queued_at": { + "name": "last_queued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "trigger_type": { + "name": "trigger_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "failed_count": { + "name": "failed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "infra_retry_count": { + "name": "infra_retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_failed_at": { + "name": "last_failed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'workflow'" + }, + "job_title": { + "name": "job_title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prompt": { + "name": "prompt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lifecycle": { + "name": "lifecycle", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'persistent'" + }, + "success_condition": { + "name": "success_condition", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "max_runs": { + "name": "max_runs", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "run_count": { + "name": "run_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "source_chat_id": { + "name": "source_chat_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_task_name": { + "name": "source_task_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_user_id": { + "name": "source_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_workspace_id": { + "name": "source_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_scope": { + "name": "secret_scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'all'" + }, + "mounted_secrets": { + "name": "mounted_secrets", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "job_history": { + "name": "job_history", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "contexts": { + "name": "contexts", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "excluded_dates": { + "name": "excluded_dates", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "ends_at": { + "name": "ends_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_schedule_workflow_block_deployment_unique": { + "name": "workflow_schedule_workflow_block_deployment_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow_schedule\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_workflow_deployment_idx": { + "name": "workflow_schedule_workflow_deployment_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_archived_at_partial_idx": { + "name": "workflow_schedule_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_schedule\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_workflow_schedule_on_source_workspace_id_source_t_c07f3bba6": { + "name": "idx_workflow_schedule_on_source_workspace_id_source_t_c07f3bba6", + "columns": [ + { + "expression": "source_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_due_workflow_idx": { + "name": "workflow_schedule_due_workflow_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_schedule\".\"archived_at\" IS NULL AND \"workflow_schedule\".\"status\" NOT IN ('disabled', 'completed') AND (\"workflow_schedule\".\"source_type\" = 'workflow' OR \"workflow_schedule\".\"source_type\" IS NULL)", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_due_job_idx": { + "name": "workflow_schedule_due_job_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_schedule\".\"archived_at\" IS NULL AND \"workflow_schedule\".\"status\" NOT IN ('disabled', 'completed') AND \"workflow_schedule\".\"source_type\" = 'job'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_schedule_workflow_id_workflow_id_fk": { + "name": "workflow_schedule_workflow_id_workflow_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "workflow_schedule_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow_deployment_version", + "columnsFrom": ["deployment_version_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_deployment_operation_id_workflow_deployment_operation_id_fk": { + "name": "workflow_schedule_deployment_operation_id_workflow_deployment_operation_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow_deployment_operation", + "columnsFrom": ["deployment_operation_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workflow_schedule_source_user_id_user_id_fk": { + "name": "workflow_schedule_source_user_id_user_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "user", + "columnsFrom": ["source_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_source_workspace_id_workspace_id_fk": { + "name": "workflow_schedule_source_workspace_id_workspace_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workspace", + "columnsFrom": ["source_workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_subflows": { + "name": "workflow_subflows", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_subflows_workflow_id_idx": { + "name": "workflow_subflows_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_subflows_workflow_type_idx": { + "name": "workflow_subflows_workflow_type_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_subflows_workflow_id_workflow_id_fk": { + "name": "workflow_subflows_workflow_id_workflow_id_fk", + "tableFrom": "workflow_subflows", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace": { + "name": "workspace", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'#33C482'" + }, + "logo_url": { + "name": "logo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_mode": { + "name": "workspace_mode", + "type": "workspace_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'grandfathered_shared'" + }, + "billed_account_user_id": { + "name": "billed_account_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "storage_used_bytes": { + "name": "storage_used_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "allow_personal_api_keys": { + "name": "allow_personal_api_keys", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "inbox_enabled": { + "name": "inbox_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "inbox_address": { + "name": "inbox_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "inbox_provider_id": { + "name": "inbox_provider_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "inbox_secret_scope": { + "name": "inbox_secret_scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'all'" + }, + "inbox_mounted_secrets": { + "name": "inbox_mounted_secrets", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "organization_assigned_at": { + "name": "organization_assigned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "forked_from_workspace_id": { + "name": "forked_from_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_owner_id_idx": { + "name": "workspace_owner_id_idx", + "columns": [ + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_organization_id_idx": { + "name": "workspace_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_mode_idx": { + "name": "workspace_mode_idx", + "columns": [ + { + "expression": "workspace_mode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_forked_from_workspace_id_idx": { + "name": "workspace_forked_from_workspace_id_idx", + "columns": [ + { + "expression": "forked_from_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_inbox_provider_id_idx": { + "name": "workspace_inbox_provider_id_idx", + "columns": [ + { + "expression": "inbox_provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace\".\"inbox_provider_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_owner_id_user_id_fk": { + "name": "workspace_owner_id_user_id_fk", + "tableFrom": "workspace", + "tableTo": "user", + "columnsFrom": ["owner_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_organization_id_organization_id_fk": { + "name": "workspace_organization_id_organization_id_fk", + "tableFrom": "workspace", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_billed_account_user_id_user_id_fk": { + "name": "workspace_billed_account_user_id_user_id_fk", + "tableFrom": "workspace", + "tableTo": "user", + "columnsFrom": ["billed_account_user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workspace_forked_from_workspace_id_workspace_id_fk": { + "name": "workspace_forked_from_workspace_id_workspace_id_fk", + "tableFrom": "workspace", + "tableTo": "workspace", + "columnsFrom": ["forked_from_workspace_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "workspace_storage_used_bytes_non_negative": { + "name": "workspace_storage_used_bytes_non_negative", + "value": "\"workspace\".\"storage_used_bytes\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.workspace_byok_keys": { + "name": "workspace_byok_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "encrypted_api_key": { + "name": "encrypted_api_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_byok_workspace_provider_idx": { + "name": "workspace_byok_workspace_provider_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_byok_keys_workspace_id_workspace_id_fk": { + "name": "workspace_byok_keys_workspace_id_workspace_id_fk", + "tableFrom": "workspace_byok_keys", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_byok_keys_created_by_user_id_fk": { + "name": "workspace_byok_keys_created_by_user_id_fk", + "tableFrom": "workspace_byok_keys", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_environment": { + "name": "workspace_environment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "variables": { + "name": "variables", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_environment_workspace_unique": { + "name": "workspace_environment_workspace_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_environment_workspace_id_workspace_id_fk": { + "name": "workspace_environment_workspace_id_workspace_id_fk", + "tableFrom": "workspace_environment", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file": { + "name": "workspace_file", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "uploaded_by": { + "name": "uploaded_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_file_workspace_id_idx": { + "name": "workspace_file_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_deleted_at_idx": { + "name": "workspace_file_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_workspace_deleted_partial_idx": { + "name": "workspace_file_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workspace_file\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_file_workspace_id_workspace_id_fk": { + "name": "workspace_file_workspace_id_workspace_id_fk", + "tableFrom": "workspace_file", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_file_uploaded_by_user_id_fk": { + "name": "workspace_file_uploaded_by_user_id_fk", + "tableFrom": "workspace_file", + "tableTo": "user", + "columnsFrom": ["uploaded_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workspace_file_key_unique": { + "name": "workspace_file_key_unique", + "nullsNotDistinct": false, + "columns": ["key"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_collab_state": { + "name": "workspace_file_collab_state", + "schema": "", + "columns": { + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "doc_state": { + "name": "doc_state", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "source_hash": { + "name": "source_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workspace_file_collab_state_file_id_workspace_files_id_fk": { + "name": "workspace_file_collab_state_file_id_workspace_files_id_fk", + "tableFrom": "workspace_file_collab_state", + "tableTo": "workspace_files", + "columnsFrom": ["file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_search_backfill": { + "name": "workspace_file_search_backfill", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "after_workspace_id": { + "name": "after_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "after_file_id": { + "name": "after_file_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_search_dispatch_queue": { + "name": "workspace_file_search_dispatch_queue", + "schema": "", + "columns": { + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "enqueued_at": { + "name": "enqueued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_dispatched_at": { + "name": "last_dispatched_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_file_search_dispatch_queue_schedule_idx": { + "name": "workspace_file_search_dispatch_queue_schedule_idx", + "columns": [ + { + "expression": "last_dispatched_at", + "isExpression": false, + "asc": true, + "nulls": "first" + }, + { + "expression": "enqueued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_file_search_queue_workspace_fk": { + "name": "workspace_file_search_queue_workspace_fk", + "tableFrom": "workspace_file_search_dispatch_queue", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_search_index": { + "name": "workspace_file_search_index", + "schema": "", + "columns": { + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_content_updated_at": { + "name": "source_content_updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "workspace_file_search_index_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "partial": { + "name": "partial", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "line_count": { + "name": "line_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "indexed_bytes": { + "name": "indexed_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "dispatched_at": { + "name": "dispatched_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_file_search_index_workspace_status_idx": { + "name": "workspace_file_search_index_workspace_status_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_content_updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_search_index_pending_dispatch_idx": { + "name": "workspace_file_search_index_pending_dispatch_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_content_updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workspace_file_search_index\".\"status\" = 'pending' AND \"workspace_file_search_index\".\"dispatched_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_search_index_active_dispatch_idx": { + "name": "workspace_file_search_index_active_dispatch_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "dispatched_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workspace_file_search_index\".\"status\" = 'pending' AND \"workspace_file_search_index\".\"dispatched_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_file_search_index_file_fk": { + "name": "workspace_file_search_index_file_fk", + "tableFrom": "workspace_file_search_index", + "tableTo": "workspace_files", + "columnsFrom": ["file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_file_search_index_workspace_fk": { + "name": "workspace_file_search_index_workspace_fk", + "tableFrom": "workspace_file_search_index", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "workspace_file_search_index_pk": { + "name": "workspace_file_search_index_pk", + "columns": ["file_id", "source_content_updated_at"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_search_segment": { + "name": "workspace_file_search_segment", + "schema": "", + "columns": { + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_content_updated_at": { + "name": "source_content_updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "line_number": { + "name": "line_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "segment_number": { + "name": "segment_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "segment_start": { + "name": "segment_start", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "line_length": { + "name": "line_length", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "workspace_file_search_segment_workspace_revision_idx": { + "name": "workspace_file_search_segment_workspace_revision_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_content_updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_search_segment_workspace_content_trgm_idx": { + "name": "workspace_file_search_segment_workspace_content_trgm_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "text_ops" + }, + { + "expression": "content", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "workspace_file_search_segment_file_fk": { + "name": "workspace_file_search_segment_file_fk", + "tableFrom": "workspace_file_search_segment", + "tableTo": "workspace_files", + "columnsFrom": ["file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_file_search_segment_workspace_fk": { + "name": "workspace_file_search_segment_workspace_fk", + "tableFrom": "workspace_file_search_segment", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "workspace_file_search_segment_pk": { + "name": "workspace_file_search_segment_pk", + "columns": ["file_id", "source_content_updated_at", "line_number", "segment_number"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_secret_provenance": { + "name": "workspace_file_secret_provenance", + "schema": "", + "columns": { + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "content_updated_at": { + "name": "content_updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entries": { + "name": "entries", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workspace_file_secret_provenance_file_id_workspace_files_id_fk": { + "name": "workspace_file_secret_provenance_file_id_workspace_files_id_fk", + "tableFrom": "workspace_file_secret_provenance", + "tableTo": "workspace_files", + "columnsFrom": ["file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "workspace_file_secret_provenance_status_check": { + "name": "workspace_file_secret_provenance_status_check", + "value": "\"workspace_file_secret_provenance\".\"status\" IN ('exact', 'unknown', 'unrecorded')" + } + }, + "isRLSEnabled": false + }, + "public.workspace_files": { + "name": "workspace_files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "folder_id": { + "name": "folder_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "context": { + "name": "context", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "original_name": { + "name": "original_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size_bytes": { + "name": "size_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "width": { + "name": "width", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "height": { + "name": "height", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "content_updated_at": { + "name": "content_updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "date_trunc('milliseconds', now())" + }, + "secret_provenance_version": { + "name": "secret_provenance_version", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workspace_files_key_active_unique": { + "name": "workspace_files_key_active_unique", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_files\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_workspace_folder_name_active_unique": { + "name": "workspace_files_workspace_folder_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"folder_id\", '')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "original_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_files\".\"deleted_at\" IS NULL AND \"workspace_files\".\"context\" = 'workspace' AND \"workspace_files\".\"workspace_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_chat_display_name_unique": { + "name": "workspace_files_chat_display_name_unique", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_files\".\"context\" = 'mothership' AND \"workspace_files\".\"chat_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_organization_id_idx": { + "name": "workspace_files_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_key_idx": { + "name": "workspace_files_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_user_id_idx": { + "name": "workspace_files_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_workspace_id_idx": { + "name": "workspace_files_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_folder_id_idx": { + "name": "workspace_files_folder_id_idx", + "columns": [ + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_context_idx": { + "name": "workspace_files_context_idx", + "columns": [ + { + "expression": "context", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_chat_id_idx": { + "name": "workspace_files_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_deleted_at_idx": { + "name": "workspace_files_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_workspace_deleted_partial_idx": { + "name": "workspace_files_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workspace_files\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_files_user_id_user_id_fk": { + "name": "workspace_files_user_id_user_id_fk", + "tableFrom": "workspace_files", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_files_workspace_id_workspace_id_fk": { + "name": "workspace_files_workspace_id_workspace_id_fk", + "tableFrom": "workspace_files", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_files_organization_id_organization_id_fk": { + "name": "workspace_files_organization_id_organization_id_fk", + "tableFrom": "workspace_files", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_files_folder_id_folder_id_fk": { + "name": "workspace_files_folder_id_folder_id_fk", + "tableFrom": "workspace_files", + "tableTo": "folder", + "columnsFrom": ["folder_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_files_chat_id_copilot_chats_id_fk": { + "name": "workspace_files_chat_id_copilot_chats_id_fk", + "tableFrom": "workspace_files", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "workspace_files_organization_binding_check": { + "name": "workspace_files_organization_binding_check", + "value": "\"workspace_files\".\"organization_id\" IS NULL OR (\"workspace_files\".\"workspace_id\" IS NULL AND \"workspace_files\".\"context\" = 'knowledge-base' AND \"workspace_files\".\"folder_id\" IS NULL AND \"workspace_files\".\"chat_id\" IS NULL)" + } + }, + "isRLSEnabled": false + }, + "public.workspace_fork_block_map": { + "name": "workspace_fork_block_map", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "child_workspace_id": { + "name": "child_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_workflow_id": { + "name": "parent_workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_block_id": { + "name": "parent_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "child_workflow_id": { + "name": "child_workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "child_block_id": { + "name": "child_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_fork_block_map_child_ws_parent_unique": { + "name": "workspace_fork_block_map_child_ws_parent_unique", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_block_map_child_ws_child_unique": { + "name": "workspace_fork_block_map_child_ws_child_unique", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "child_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_block_map_child_ws_parent_wf_idx": { + "name": "workspace_fork_block_map_child_ws_parent_wf_idx", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_block_map_child_ws_child_wf_idx": { + "name": "workspace_fork_block_map_child_ws_child_wf_idx", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "child_workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_fork_block_map_child_workspace_id_workspace_id_fk": { + "name": "workspace_fork_block_map_child_workspace_id_workspace_id_fk", + "tableFrom": "workspace_fork_block_map", + "tableTo": "workspace", + "columnsFrom": ["child_workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_fork_dependent_value": { + "name": "workspace_fork_dependent_value", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "child_workspace_id": { + "name": "child_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_workflow_id": { + "name": "target_workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_block_id": { + "name": "target_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sub_block_key": { + "name": "sub_block_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_fork_dependent_value_child_ws_wf_idx": { + "name": "workspace_fork_dependent_value_child_ws_wf_idx", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_dependent_value_field_unique": { + "name": "workspace_fork_dependent_value_field_unique", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sub_block_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_fork_dependent_value_child_workspace_id_workspace_id_fk": { + "name": "workspace_fork_dependent_value_child_workspace_id_workspace_id_fk", + "tableFrom": "workspace_fork_dependent_value", + "tableTo": "workspace", + "columnsFrom": ["child_workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_fork_promote_run": { + "name": "workspace_fork_promote_run", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "child_workspace_id": { + "name": "child_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_workspace_id": { + "name": "source_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_workspace_id": { + "name": "target_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "direction": { + "name": "direction", + "type": "workspace_fork_promote_direction", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "snapshot": { + "name": "snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_fork_promote_run_child_ws_target_unique": { + "name": "workspace_fork_promote_run_child_ws_target_unique", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_promote_run_target_ws_idx": { + "name": "workspace_fork_promote_run_target_ws_idx", + "columns": [ + { + "expression": "target_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_fork_promote_run_child_workspace_id_workspace_id_fk": { + "name": "workspace_fork_promote_run_child_workspace_id_workspace_id_fk", + "tableFrom": "workspace_fork_promote_run", + "tableTo": "workspace", + "columnsFrom": ["child_workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_fork_promote_run_created_by_user_id_fk": { + "name": "workspace_fork_promote_run_created_by_user_id_fk", + "tableFrom": "workspace_fork_promote_run", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_fork_resource_map": { + "name": "workspace_fork_resource_map", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "child_workspace_id": { + "name": "child_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "workspace_fork_resource_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "parent_resource_id": { + "name": "parent_resource_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "child_resource_id": { + "name": "child_resource_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_fork_resource_map_child_ws_idx": { + "name": "workspace_fork_resource_map_child_ws_idx", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_resource_map_child_ws_type_idx": { + "name": "workspace_fork_resource_map_child_ws_type_idx", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_resource_map_child_type_parent_unique": { + "name": "workspace_fork_resource_map_child_type_parent_unique", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_fork_resource_map_child_workspace_id_workspace_id_fk": { + "name": "workspace_fork_resource_map_child_workspace_id_workspace_id_fk", + "tableFrom": "workspace_fork_resource_map", + "tableTo": "workspace", + "columnsFrom": ["child_workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_fork_resource_map_created_by_user_id_fk": { + "name": "workspace_fork_resource_map_created_by_user_id_fk", + "tableFrom": "workspace_fork_resource_map", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_operation_receipt": { + "name": "workspace_operation_receipt", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_hash": { + "name": "request_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "report": { + "name": "report", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_operation_receipt_request_unique": { + "name": "workspace_operation_receipt_request_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_operation_receipt_workspace_created_idx": { + "name": "workspace_operation_receipt_workspace_created_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_operation_receipt_workspace_id_workspace_id_fk": { + "name": "workspace_operation_receipt_workspace_id_workspace_id_fk", + "tableFrom": "workspace_operation_receipt", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_sandbox": { + "name": "workspace_sandbox", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "language": { + "name": "language", + "type": "sandbox_language", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "dependencies": { + "name": "dependencies", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "cli_tools": { + "name": "cli_tools", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "system_packages": { + "name": "system_packages", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "spec_hash": { + "name": "spec_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_sandbox_workspace_name_unique": { + "name": "workspace_sandbox_workspace_name_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_sandbox_workspace_idx": { + "name": "workspace_sandbox_workspace_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_sandbox_spec_hash_idx": { + "name": "workspace_sandbox_spec_hash_idx", + "columns": [ + { + "expression": "spec_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_sandbox_workspace_id_workspace_id_fk": { + "name": "workspace_sandbox_workspace_id_workspace_id_fk", + "tableFrom": "workspace_sandbox", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_sandbox_created_by_user_id_fk": { + "name": "workspace_sandbox_created_by_user_id_fk", + "tableFrom": "workspace_sandbox", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.academy_cert_status": { + "name": "academy_cert_status", + "schema": "public", + "values": ["active", "revoked", "expired"] + }, + "public.background_work_kind": { + "name": "background_work_kind", + "schema": "public", + "values": ["deployment_side_effects", "fork_content_copy", "fork_sync", "fork_rollback"] + }, + "public.background_work_status_value": { + "name": "background_work_status_value", + "schema": "public", + "values": ["pending", "processing", "completed", "completed_with_warnings", "failed"] + }, + "public.billing_blocked_reason": { + "name": "billing_blocked_reason", + "schema": "public", + "values": ["payment_failed", "dispute"] + }, + "public.billing_entity_type": { + "name": "billing_entity_type", + "schema": "public", + "values": ["user", "organization"] + }, + "public.chat_type": { + "name": "chat_type", + "schema": "public", + "values": ["mothership", "copilot"] + }, + "public.copilot_async_tool_status": { + "name": "copilot_async_tool_status", + "schema": "public", + "values": ["pending", "running", "completed", "failed", "cancelled", "delivered"] + }, + "public.copilot_run_status": { + "name": "copilot_run_status", + "schema": "public", + "values": ["active", "paused_waiting_for_tool", "resuming", "complete", "error", "cancelled"] + }, + "public.copilot_tool_permission_decision": { + "name": "copilot_tool_permission_decision", + "schema": "public", + "values": ["allow", "allow_chat", "always_allow", "skip"] + }, + "public.credential_group_enrollment_status": { + "name": "credential_group_enrollment_status", + "schema": "public", + "values": ["invited", "delivery_failed", "in_progress", "completed", "revoked"] + }, + "public.credential_group_status": { + "name": "credential_group_status", + "schema": "public", + "values": ["active", "disabled"] + }, + "public.credential_member_role": { + "name": "credential_member_role", + "schema": "public", + "values": ["admin", "member"] + }, + "public.credential_member_status": { + "name": "credential_member_status", + "schema": "public", + "values": ["active", "pending", "revoked"] + }, + "public.credential_type": { + "name": "credential_type", + "schema": "public", + "values": [ + "oauth", + "managed_oauth", + "managed_mcp", + "env_workspace", + "env_personal", + "service_account", + "personal_token" + ] + }, + "public.data_drain_cadence": { + "name": "data_drain_cadence", + "schema": "public", + "values": ["hourly", "daily"] + }, + "public.data_drain_destination": { + "name": "data_drain_destination", + "schema": "public", + "values": ["s3", "gcs", "azure_blob", "datadog", "bigquery", "snowflake", "webhook"] + }, + "public.data_drain_run_status": { + "name": "data_drain_run_status", + "schema": "public", + "values": ["running", "success", "failed"] + }, + "public.data_drain_run_trigger": { + "name": "data_drain_run_trigger", + "schema": "public", + "values": ["cron", "manual"] + }, + "public.data_drain_source": { + "name": "data_drain_source", + "schema": "public", + "values": ["workflow_logs", "job_logs", "audit_logs", "copilot_chats", "copilot_runs"] + }, + "public.execution_large_value_reference_source": { + "name": "execution_large_value_reference_source", + "schema": "public", + "values": ["execution_log", "paused_snapshot"] + }, + "public.folder_resource_type": { + "name": "folder_resource_type", + "schema": "public", + "values": ["workflow", "file", "knowledge_base", "table"] + }, + "public.invitation_kind": { + "name": "invitation_kind", + "schema": "public", + "values": ["organization", "workspace"] + }, + "public.invitation_membership_intent": { + "name": "invitation_membership_intent", + "schema": "public", + "values": ["internal", "external"] + }, + "public.invitation_status": { + "name": "invitation_status", + "schema": "public", + "values": ["pending", "accepted", "rejected", "cancelled", "expired"] + }, + "public.managed_oauth_credential_status": { + "name": "managed_oauth_credential_status", + "schema": "public", + "values": ["active", "needs_reauth", "revoked"] + }, + "public.permission_type": { + "name": "permission_type", + "schema": "public", + "values": ["admin", "write", "read"] + }, + "public.sandbox_image_status": { + "name": "sandbox_image_status", + "schema": "public", + "values": ["pending", "building", "ready", "failed"] + }, + "public.sandbox_language": { + "name": "sandbox_language", + "schema": "public", + "values": ["javascript", "python"] + }, + "public.secret_usage_scope": { + "name": "secret_usage_scope", + "schema": "public", + "values": ["workspace", "personal"] + }, + "public.secret_usage_source": { + "name": "secret_usage_source", + "schema": "public", + "values": ["workflow", "copilot", "mcp"] + }, + "public.upload_session_method": { + "name": "upload_session_method", + "schema": "public", + "values": ["put", "multipart"] + }, + "public.upload_session_provider": { + "name": "upload_session_provider", + "schema": "public", + "values": ["local", "s3", "blob", "gcs"] + }, + "public.upload_session_purpose": { + "name": "upload_session_purpose", + "schema": "public", + "values": [ + "workspace_file", + "table_import", + "knowledge_document", + "profile_picture", + "workspace_logo", + "organization_logo", + "mothership_attachment", + "execution_attachment" + ] + }, + "public.upload_session_status": { + "name": "upload_session_status", + "schema": "public", + "values": [ + "uploading", + "completing", + "finalizing", + "completed", + "aborting", + "aborted", + "failed", + "expired" + ] + }, + "public.usage_log_category": { + "name": "usage_log_category", + "schema": "public", + "values": ["model", "fixed", "tool", "model_unbilled"] + }, + "public.usage_log_source": { + "name": "usage_log_source", + "schema": "public", + "values": [ + "workflow", + "wand", + "copilot", + "workspace-chat", + "mcp_copilot", + "mothership_block", + "knowledge-base", + "voice-input", + "enrichment", + "voice-output", + "api-tool" + ] + }, + "public.workspace_file_search_index_status": { + "name": "workspace_file_search_index_status", + "schema": "public", + "values": ["pending", "ready", "skipped", "failed"] + }, + "public.workspace_fork_promote_direction": { + "name": "workspace_fork_promote_direction", + "schema": "public", + "values": ["push", "pull"] + }, + "public.workspace_fork_resource_type": { + "name": "workspace_fork_resource_type", + "schema": "public", + "values": [ + "workflow", + "oauth_credential", + "service_account_credential", + "env_var", + "table", + "knowledge_base", + "knowledge_document", + "file", + "file_folder", + "mcp_server", + "workflow_mcp_server", + "custom_block", + "custom_tool", + "skill", + "sandbox" + ] + }, + "public.workspace_mode": { + "name": "workspace_mode", + "schema": "public", + "values": ["personal", "organization", "grandfathered_shared"] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/packages/db/migrations/meta/_journal.json b/packages/db/migrations/meta/_journal.json index 4957be9db64..4977de46525 100644 --- a/packages/db/migrations/meta/_journal.json +++ b/packages/db/migrations/meta/_journal.json @@ -2500,6 +2500,13 @@ "when": 1789677432855, "tag": "0357_outbox_autovacuum", "breakpoints": true + }, + { + "idx": 358, + "version": "7", + "when": 1789678807393, + "tag": "0358_workspace_file_content_version_precision", + "breakpoints": true } ] } diff --git a/packages/db/schema.ts b/packages/db/schema.ts index 48db7dfb4b9..c60bd2c3882 100644 --- a/packages/db/schema.ts +++ b/packages/db/schema.ts @@ -2261,8 +2261,20 @@ export const workspaceFiles = pgTable( * with a `now()` default: Postgres applies this as a fast-default (no table rewrite), existing rows * get a stable timestamp that — like every metadata write — never advances it, and every insert path * is covered without per-call plumbing. Only a content write (upload / overwrite) advances it. + * + * MILLISECOND precision is an invariant, not an incidental detail. This value is a revision identity + * that round-trips through JavaScript `Date` and JSON — search-index dispatch payloads, If-Match + * tokens, realtime versions — all of which truncate to milliseconds, while `now()` stores + * microseconds. A sub-millisecond value therefore stops comparing equal to its own round-trip, and + * every SQL equality keyed on it matches zero rows: a file whose + * `workspace_file_search_index.source_content_updated_at` came from such a round trip can never be + * claimed, indexed, or cleaned up. The default truncates, and the + * `workspace_files_content_version_millisecond` trigger enforces it for the writers a default cannot + * reach — explicit `CURRENT_TIMESTAMP` expressions, raw SQL inserts, and any UPDATE. */ - contentUpdatedAt: timestamp('content_updated_at').notNull().defaultNow(), + contentUpdatedAt: timestamp('content_updated_at') + .notNull() + .default(sql`date_trunc('milliseconds', now())`), /** * Durable cutover marker for content secret provenance. NULL is reserved for legacy rows and * writes from app versions that predate tracking. Provenance-aware writers set version 1 in the diff --git a/packages/db/script-migrations-paused-billing-attribution.test.ts b/packages/db/script-migrations-paused-billing-attribution.test.ts index 3c75c7f1d8a..67e6005f9ae 100644 --- a/packages/db/script-migrations-paused-billing-attribution.test.ts +++ b/packages/db/script-migrations-paused-billing-attribution.test.ts @@ -451,6 +451,7 @@ describe('script migration registry', () => { '0014_require_knowledge_base_owner', '0016_backfill_search_vectors', '0017_index_search_documents', + '0018_repair_workspace_file_content_revision', ]) }) }) diff --git a/packages/db/script-migrations/0016_backfill_search_vectors.postgres.test.ts b/packages/db/script-migrations/0016_backfill_search_vectors.postgres.test.ts index e8f6265debb..869388086d3 100644 --- a/packages/db/script-migrations/0016_backfill_search_vectors.postgres.test.ts +++ b/packages/db/script-migrations/0016_backfill_search_vectors.postgres.test.ts @@ -389,6 +389,7 @@ describe.runIf(Boolean(databaseUrl))('search projection upgrade in PostgreSQL', { name: '0015_backfill_embedding_search' }, { name: '0016_backfill_search_vectors' }, { name: '0017_index_search_documents' }, + { name: '0018_repair_workspace_file_content_revision' }, ]) const [{ complete }] = await sql`SELECT count(*)::int AS complete FROM embedding e JOIN embedding_search s ON s.id = e.id JOIN embedding_keyword_search k ON k.id = e.id diff --git a/packages/db/script-migrations/0018_repair_workspace_file_content_revision.postgres.test.ts b/packages/db/script-migrations/0018_repair_workspace_file_content_revision.postgres.test.ts new file mode 100644 index 00000000000..61f99c268d8 --- /dev/null +++ b/packages/db/script-migrations/0018_repair_workspace_file_content_revision.postgres.test.ts @@ -0,0 +1,320 @@ +import { readFileSync } from 'node:fs' +import path from 'node:path' +import { repairWorkspaceFileContentRevisions } from '@sim/db/script-migrations/0018_repair_workspace_file_content_revision' +import { generateId } from '@sim/utils/id' +import postgres, { type Sql } from 'postgres' +import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest' + +const databaseUrl = process.env.KNOWLEDGE_ACL_TEST_DATABASE_URL +const MICROSECOND_REVISION = '2026-08-01 03:30:51.566952' +const MILLISECOND_REVISION = '2026-08-01 03:30:51.566' +const MIGRATIONS = path.join(__dirname, '../migrations') + +/** Applies the statements of a shipped migration that `matches` selects, against the scratch schema. */ +async function applyMigration( + sql: Sql, + file: string, + matches: (statement: string) => boolean +): Promise { + const statements = readFileSync(path.join(MIGRATIONS, file), 'utf8') + .split('--> statement-breakpoint') + .map((statement) => statement.trim()) + .filter(matches) + if (statements.length === 0) throw new Error(`No statements matched in ${file}`) + for (const statement of statements) await sql.unsafe(statement) +} + +describe.runIf(Boolean(databaseUrl))('workspace file content revision repair in PostgreSQL', () => { + let sql: Sql + let admin: Sql + const schemaName = `revision_repair_${generateId().replaceAll('-', '')}` + + beforeAll(async () => { + const url = new URL(databaseUrl!) + if ( + !['localhost', '127.0.0.1'].includes(url.hostname) || + (!url.pathname.startsWith('/sim_acl_test') && url.pathname !== '/sim_auth_scim') + ) { + throw new Error('Repair tests require a disposable local integration database') + } + admin = postgres(url.toString(), { max: 1, onnotice: () => undefined }) + await admin.unsafe(`CREATE SCHEMA "${schemaName}"`) + sql = postgres(url.toString(), { + max: 2, + onnotice: () => undefined, + connection: { search_path: schemaName, TimeZone: 'UTC' }, + }) + await sql`CREATE TABLE workspace_files ( + id text PRIMARY KEY, workspace_id text, context text NOT NULL, deleted_at timestamp, + content_updated_at timestamp NOT NULL DEFAULT now(), secret_provenance_version integer + )` + await sql`CREATE TABLE workspace_file_secret_provenance ( + file_id text PRIMARY KEY REFERENCES workspace_files(id) ON DELETE CASCADE, + content_updated_at timestamp NOT NULL, status text NOT NULL, + entries jsonb NOT NULL DEFAULT '[]', updated_at timestamp NOT NULL DEFAULT now() + )` + await sql`CREATE TABLE workspace_file_search_index ( + file_id text NOT NULL, workspace_id text NOT NULL, source_content_updated_at timestamp NOT NULL, + status text NOT NULL DEFAULT 'pending', partial boolean NOT NULL DEFAULT false, + failure_reason text, line_count integer NOT NULL DEFAULT 0, + indexed_bytes integer NOT NULL DEFAULT 0, dispatched_at timestamp, + created_at timestamp NOT NULL DEFAULT now(), updated_at timestamp NOT NULL DEFAULT now(), + PRIMARY KEY (file_id, source_content_updated_at) + )` + await sql`CREATE TABLE workspace_file_search_segment ( + file_id text NOT NULL, workspace_id text NOT NULL, source_content_updated_at timestamp NOT NULL, + line_number integer NOT NULL, segment_number integer NOT NULL, content text NOT NULL, + line_length integer NOT NULL DEFAULT 0, segment_start integer NOT NULL DEFAULT 0, + PRIMARY KEY (file_id, source_content_updated_at, line_number, segment_number) + )` + await sql`CREATE TABLE workspace_file_search_dispatch_queue ( + workspace_id text PRIMARY KEY, enqueued_at timestamp NOT NULL DEFAULT now(), + updated_at timestamp NOT NULL DEFAULT now(), last_dispatched_at timestamp + )` + await applyMigration( + sql, + '0313_puzzling_zodiak.sql', + (statement) => + statement.includes('workspace_file_search_mark_pending') && + (statement.startsWith('CREATE OR REPLACE FUNCTION') || + statement.startsWith('CREATE TRIGGER')) + ) + await applyMigration( + sql, + '0283_military_fabian_cortez.sql', + (statement) => + statement.includes('FUNCTION "demote_secret_provenance_version"') && + statement.includes('CREATE OR REPLACE') + ) + await sql`CREATE TRIGGER workspace_files_secret_provenance_demote + BEFORE UPDATE OF content_updated_at ON workspace_files FOR EACH ROW + WHEN (OLD.content_updated_at IS DISTINCT FROM NEW.content_updated_at) + EXECUTE FUNCTION demote_secret_provenance_version()` + await applyMigration( + sql, + '0358_workspace_file_content_version_precision.sql', + (statement) => + statement.includes('content_version_millisecond') || + statement.includes('SET DEFAULT date_trunc') + ) + }) + + beforeEach(async () => { + await sql`TRUNCATE workspace_files, workspace_file_secret_provenance, + workspace_file_search_index, workspace_file_search_segment, + workspace_file_search_dispatch_queue` + }) + + afterAll(async () => { + try { + await admin?.unsafe(`DROP SCHEMA IF EXISTS "${schemaName}" CASCADE`) + } finally { + await sql?.end() + await admin?.end() + } + }) + + /** + * Seeds a row that predates the 0358 trigger, which would otherwise normalize it on the way in. + * Timestamps go in as SQL literals: the driver serializes a bound `timestamp` parameter through a + * JavaScript `Date`, which is the very truncation under test. + */ + async function seedLegacyFile(options: { + id: string + context: string + deleted?: boolean + provenance?: number + }) { + const deletedAt = options.deleted ? `TIMESTAMP '2026-09-01 00:00:00'` : 'NULL' + const provenance = options.provenance ?? 'NULL' + await sql`ALTER TABLE workspace_files DISABLE TRIGGER USER` + await sql.unsafe(`INSERT INTO workspace_files + (id, workspace_id, context, deleted_at, content_updated_at, secret_provenance_version) + VALUES ('${options.id}', 'legacy-workspace', '${options.context}', ${deletedAt}, + TIMESTAMP '${MICROSECOND_REVISION}', ${provenance})`) + await sql`ALTER TABLE workspace_files ENABLE TRIGGER USER` + } + + async function seedSidecar(fileId: string, revision: string) { + await sql.unsafe(`INSERT INTO workspace_file_secret_provenance + (file_id, content_updated_at, status) + VALUES ('${fileId}', TIMESTAMP '${revision}', 'exact')`) + } + + async function seedIndexRow(fileId: string, revision: string, dispatched: boolean) { + await sql.unsafe(`INSERT INTO workspace_file_search_index + (file_id, workspace_id, source_content_updated_at, status, dispatched_at) + VALUES ('${fileId}', 'legacy-workspace', TIMESTAMP '${revision}', 'pending', + ${dispatched ? 'now()' : 'NULL'})`) + } + + async function revisionOf(fileId: string): Promise { + const [row] = await sql<{ revision: string }[]>`SELECT content_updated_at::text AS revision + FROM workspace_files WHERE id = ${fileId}` + return row.revision + } + + it('repairs live and soft-deleted workspace rows and leaves other contexts alone', async () => { + await seedLegacyFile({ id: 'live', context: 'workspace' }) + await seedLegacyFile({ id: 'deleted', context: 'workspace', deleted: true }) + await seedLegacyFile({ id: 'knowledge', context: 'knowledge-base' }) + + const repaired = await repairWorkspaceFileContentRevisions(sql, 2) + + expect(repaired).toBe(2) + expect(await revisionOf('live')).toBe(MILLISECOND_REVISION) + expect(await revisionOf('deleted')).toBe(MILLISECOND_REVISION) + expect(await revisionOf('knowledge')).toBe(MICROSECOND_REVISION) + }) + + it('keeps a tracked provenance version and realigns its sidecar', async () => { + await seedLegacyFile({ id: 'tracked', context: 'workspace', provenance: 1 }) + await seedSidecar('tracked', MILLISECOND_REVISION) + + await repairWorkspaceFileContentRevisions(sql) + + const [row] = await sql<{ version: number; sidecarMatches: boolean }[]>` + SELECT file.secret_provenance_version AS version, + (sidecar.content_updated_at = file.content_updated_at) AS "sidecarMatches" + FROM workspace_files AS file + JOIN workspace_file_secret_provenance AS sidecar ON sidecar.file_id = file.id + WHERE file.id = 'tracked'` + expect(row).toEqual({ version: 1, sidecarMatches: true }) + }) + + it('leaves the search index holding exactly the revision the file now records', async () => { + await seedLegacyFile({ id: 'stranded', context: 'workspace' }) + await seedIndexRow('stranded', MILLISECOND_REVISION, false) + await seedIndexRow('stranded', MICROSECOND_REVISION, true) + await sql.unsafe(`INSERT INTO workspace_file_search_segment + (file_id, workspace_id, source_content_updated_at, line_number, segment_number, content) + VALUES ('stranded', 'legacy-workspace', TIMESTAMP '${MICROSECOND_REVISION}', 1, 1, 'orphaned')`) + + await repairWorkspaceFileContentRevisions(sql) + + const rows = await sql<{ revision: string; matchesFile: boolean }[]>` + SELECT search_index.source_content_updated_at::text AS revision, + (search_index.source_content_updated_at = file.content_updated_at) AS "matchesFile" + FROM workspace_file_search_index AS search_index + JOIN workspace_files AS file ON file.id = search_index.file_id + WHERE search_index.file_id = 'stranded'` + expect([...rows]).toEqual([{ revision: MILLISECOND_REVISION, matchesFile: true }]) + const [segments] = await sql<{ remaining: number }[]>`SELECT count(*)::int AS remaining + FROM workspace_file_search_segment WHERE file_id = 'stranded'` + expect(segments.remaining).toBe(0) + }) + + it('is a no-op on replay', async () => { + await seedLegacyFile({ id: 'replayed', context: 'workspace', provenance: 1 }) + await seedSidecar('replayed', MILLISECOND_REVISION) + await repairWorkspaceFileContentRevisions(sql) + + const repairedAgain = await repairWorkspaceFileContentRevisions(sql) + + expect(repairedAgain).toBe(0) + expect(await revisionOf('replayed')).toBe(MILLISECOND_REVISION) + const [row] = await sql<{ version: number }[]>`SELECT secret_provenance_version AS version + FROM workspace_files WHERE id = 'replayed'` + expect(row.version).toBe(1) + }) + + it('walks past rows it cannot repair instead of rescanning them', async () => { + for (let index = 0; index < 5; index += 1) { + await seedLegacyFile({ id: `file-${index}`, context: 'workspace' }) + } + + const repaired = await repairWorkspaceFileContentRevisions(sql, 2) + + expect(repaired).toBe(5) + const [row] = await sql<{ remaining: number }[]>`SELECT count(*)::int AS remaining + FROM workspace_files + WHERE content_updated_at <> date_trunc('milliseconds', content_updated_at)` + expect(row.remaining).toBe(0) + }) + + describe('the 0358 invariant', () => { + async function insertWithDatabaseDefault(id: string) { + await sql.unsafe(`INSERT INTO workspace_files (id, workspace_id, context) + VALUES ('${id}', 'legacy-workspace', 'workspace')`) + } + + async function isMillisecondPrecise(fileId: string): Promise { + const [row] = await sql<{ precise: boolean }[]>`SELECT + content_updated_at = date_trunc('milliseconds', content_updated_at) AS precise + FROM workspace_files WHERE id = ${fileId}` + return row.precise + } + + it('declares a truncating column default', async () => { + const [row] = await sql<{ expression: string }[]>`SELECT column_default AS expression + FROM information_schema.columns + WHERE table_schema = ${schemaName} AND table_name = 'workspace_files' + AND column_name = 'content_updated_at'` + + expect(row.expression).toContain("date_trunc('milliseconds'") + }) + + it('truncates a revision written with microsecond precision', async () => { + await sql.unsafe(`INSERT INTO workspace_files (id, workspace_id, context, content_updated_at) + VALUES ('explicit', 'legacy-workspace', 'workspace', TIMESTAMP '${MICROSECOND_REVISION}')`) + + expect(await revisionOf('explicit')).toBe(MILLISECOND_REVISION) + }) + + it('truncates a CURRENT_TIMESTAMP restore write', async () => { + await insertWithDatabaseDefault('restored') + + await sql`UPDATE workspace_files + SET content_updated_at = + GREATEST(CURRENT_TIMESTAMP, content_updated_at + INTERVAL '1 millisecond') + WHERE id = 'restored'` + + expect(await isMillisecondPrecise('restored')).toBe(true) + }) + + it('normalizes before provenance demotion decides whether the content changed', async () => { + await insertWithDatabaseDefault('tracked-live') + await sql`UPDATE workspace_files SET secret_provenance_version = 1 WHERE id = 'tracked-live'` + + await sql`UPDATE workspace_files + SET content_updated_at = content_updated_at + INTERVAL '400 microseconds' + WHERE id = 'tracked-live'` + + const [row] = await sql<{ version: number }[]>`SELECT secret_provenance_version AS version + FROM workspace_files WHERE id = 'tracked-live'` + expect(row.version).toBe(1) + }) + + it('normalizes a legacy row promoted into the workspace by a metadata-only write', async () => { + // Materializing a chat upload sets `context` alone, so the revision is never written; without + // normalizing here the search-index trigger would key the promoted file to an unclaimable value. + await seedLegacyFile({ id: 'promoted', context: 'mothership', provenance: 1 }) + + await sql`UPDATE workspace_files SET context = 'workspace' WHERE id = 'promoted'` + + expect(await revisionOf('promoted')).toBe(MILLISECOND_REVISION) + const [row] = await sql<{ version: number; indexed: string | null }[]>` + SELECT file.secret_provenance_version AS version, + (SELECT search_index.source_content_updated_at::text + FROM workspace_file_search_index AS search_index + WHERE search_index.file_id = file.id) AS indexed + FROM workspace_files AS file WHERE file.id = 'promoted'` + expect(row.indexed).toBe(MILLISECOND_REVISION) + expect(row.version).toBe(1) + }) + + it('still demotes provenance when the millisecond revision advances', async () => { + await insertWithDatabaseDefault('tracked-advanced') + await sql`UPDATE workspace_files + SET secret_provenance_version = 1 WHERE id = 'tracked-advanced'` + + await sql`UPDATE workspace_files + SET content_updated_at = content_updated_at + INTERVAL '1 millisecond' + WHERE id = 'tracked-advanced'` + + const [row] = await sql<{ version: number }[]>`SELECT secret_provenance_version AS version + FROM workspace_files WHERE id = 'tracked-advanced'` + expect(row.version).toBeNull() + }) + }) +}) diff --git a/packages/db/script-migrations/0018_repair_workspace_file_content_revision.ts b/packages/db/script-migrations/0018_repair_workspace_file_content_revision.ts new file mode 100644 index 00000000000..73ff7a22635 --- /dev/null +++ b/packages/db/script-migrations/0018_repair_workspace_file_content_revision.ts @@ -0,0 +1,142 @@ +import type { Sql } from 'postgres' +import type { ScriptMigration } from './types' + +export const CONTENT_REVISION_REPAIR_BATCH_SIZE = 1000 + +interface RepairPage { + scanned: number + repaired: number + lastFileId: string | null +} + +/** + * Repairs one page of revisions stored with sub-millisecond precision. + * + * `content_updated_at` must be millisecond-precise; the column's TSDoc in `packages/db/schema.ts` has why + * a sub-millisecond revision strands a file as permanently unindexed. Migration 0358 stops new ones being + * minted; these are the rows that predate it, almost all stamped by the `DEFAULT now()` that added the + * column. + * + * Scoped to `context = 'workspace'`, live and soft-deleted: those are the rows the search index keys on, + * and a soft-deleted row is restored by paths that do not rewrite `content_updated_at`, which would + * re-enter the broken state. Other contexts are never search-indexed and only ever compare this column in + * JavaScript, where both sides are already millisecond; the 0358 trigger normalizes them on their next + * content write rather than rewriting millions of rows here. + * + * Keyset by `id` so each page continues along the primary key instead of re-scanning the rows earlier + * pages already repaired — the predicate itself cannot use an index. + * + * The whole page is one transaction because `workspace_files_secret_provenance_demote` nulls + * `secret_provenance_version` on any change to `content_updated_at`. Truncation keeps the same content + * and the sidecar moves with it, so the tracked version is restored rather than left demoted to the + * legacy untracked state — and a crash between the two would otherwise lose it permanently. Locks the + * parent rows in `id` order first, the order a content write takes them, so an overlapping upload waits + * instead of deadlocking. + */ +async function repairContentRevisionPage( + sql: Sql, + batchSize: number, + afterFileId: string +): Promise { + const candidates = await sql<{ fileId: string }[]>` + SELECT id AS "fileId" + FROM workspace_files + WHERE context = 'workspace' + AND content_updated_at <> date_trunc('milliseconds', content_updated_at) + AND id > ${afterFileId} + ORDER BY id + LIMIT ${batchSize} + ` + if (candidates.length === 0) return { scanned: 0, repaired: 0, lastFileId: null } + const fileIds = candidates.map((candidate) => candidate.fileId) + const lastFileId = fileIds[fileIds.length - 1] + + const repaired = await sql.begin(async (tx) => { + const tracked = await tx<{ fileId: string; version: number | null }[]>` + SELECT id AS "fileId", secret_provenance_version AS version + FROM workspace_files + WHERE id = ANY(${fileIds}::text[]) + ORDER BY id + FOR UPDATE + ` + const rewritten = await tx<{ fileId: string }[]>` + UPDATE workspace_files + SET content_updated_at = date_trunc('milliseconds', content_updated_at) + WHERE id = ANY(${fileIds}::text[]) + AND context = 'workspace' + AND content_updated_at <> date_trunc('milliseconds', content_updated_at) + RETURNING id AS "fileId" + ` + if (rewritten.length === 0) return 0 + const rewrittenIds = rewritten.map((row) => row.fileId) + + await tx` + UPDATE workspace_file_secret_provenance + SET content_updated_at = date_trunc('milliseconds', content_updated_at) + WHERE file_id = ANY(${rewrittenIds}::text[]) + AND content_updated_at <> date_trunc('milliseconds', content_updated_at) + ` + + const rewrittenIdSet = new Set(rewrittenIds) + const restorable = tracked.filter( + (row): row is { fileId: string; version: number } => + row.version !== null && rewrittenIdSet.has(row.fileId) + ) + if (restorable.length > 0) { + await tx` + UPDATE workspace_files AS file + SET secret_provenance_version = restored.version + FROM unnest( + ${restorable.map((row) => row.fileId)}::text[], + ${restorable.map((row) => row.version)}::integer[] + ) AS restored(file_id, version) + WHERE file.id = restored.file_id + AND file.secret_provenance_version IS DISTINCT FROM restored.version + ` + } + + // These match no file row, so nothing can claim them, while an outstanding one still holds a slice + // of its workspace's dispatch budget until the stale-claim reaper runs. + await tx` + DELETE FROM workspace_file_search_segment AS segment + USING workspace_files AS file + WHERE segment.file_id = file.id + AND segment.file_id = ANY(${rewrittenIds}::text[]) + AND segment.source_content_updated_at <> file.content_updated_at + AND date_trunc('milliseconds', segment.source_content_updated_at) = file.content_updated_at + ` + await tx` + DELETE FROM workspace_file_search_index AS search_index + USING workspace_files AS file + WHERE search_index.file_id = file.id + AND search_index.file_id = ANY(${rewrittenIds}::text[]) + AND search_index.source_content_updated_at <> file.content_updated_at + AND date_trunc('milliseconds', search_index.source_content_updated_at) = + file.content_updated_at + ` + return rewritten.length + }) + + return { scanned: candidates.length, repaired, lastFileId } +} + +export async function repairWorkspaceFileContentRevisions( + sql: Sql, + batchSize: number = CONTENT_REVISION_REPAIR_BATCH_SIZE +): Promise { + let afterFileId = '' + let repaired = 0 + for (;;) { + const page = await repairContentRevisionPage(sql, batchSize, afterFileId) + if (page.scanned === 0 || page.lastFileId === null) return repaired + repaired += page.repaired + afterFileId = page.lastFileId + } +} + +export const repairWorkspaceFileContentRevisionMigration: ScriptMigration = { + name: '0018_repair_workspace_file_content_revision', + async up(sql) { + await repairWorkspaceFileContentRevisions(sql) + }, +} diff --git a/packages/db/script-migrations/index.ts b/packages/db/script-migrations/index.ts index 63f276fae8f..bd3169b4a4d 100644 --- a/packages/db/script-migrations/index.ts +++ b/packages/db/script-migrations/index.ts @@ -3,6 +3,7 @@ import { backfillLegacyKnowledgeBaseWorkspacesMigration } from '@sim/db/script-m import { requireKnowledgeBaseOwnerMigration } from '@sim/db/script-migrations/0014_require_knowledge_base_owner' import { backfillSearchVectorsMigration } from '@sim/db/script-migrations/0016_backfill_search_vectors' import { indexSearchDocumentsMigration } from '@sim/db/script-migrations/0017_index_search_documents' +import { repairWorkspaceFileContentRevisionMigration } from '@sim/db/script-migrations/0018_repair_workspace_file_content_revision' import type { Sql } from 'postgres' import { backfillTableOrderKeys } from './0001_backfill_table_order_keys' import { backfillPausedBillingAttribution } from './0002_backfill_paused_billing_attribution' @@ -38,6 +39,8 @@ export const scriptMigrations: readonly ScriptMigration[] = [ /** 0016 completes partially applied 0015 binary projections together with the new search vectors. */ backfillSearchVectorsMigration, indexSearchDocumentsMigration, + /** 0358 stops new sub-millisecond revisions; this retires the ones that predate it. */ + repairWorkspaceFileContentRevisionMigration, ] /** From bfaeabab253d94fe41fedc67caf443a6223794d5 Mon Sep 17 00:00:00 2001 From: Waleed Date: Thu, 17 Sep 2026 14:10:23 -0700 Subject: [PATCH 15/15] fix(knowledge): write external group membership as a diff (#7943) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(knowledge): write external group membership as a diff Every directory sync deleted a group's entire membership and reinserted it, whether or not anything had changed. That took the membership table to roughly 55M lifetime inserts and 55M deletes against ~127k live rows — about 430x write amplification — holding it near 91% dead tuples across 2,447 autovacuum cycles, an order of magnitude more churn than any comparable table. The vacuum load that generates competes for the same I/O as every other query on the instance. Membership is now written as a difference: read the group's current tokens, delete only those the enumeration no longer lists, insert only those it newly lists. An unchanged group writes nothing. The group row is locked before that read. The two callers fence on different leases — the directory lease and the connector sync lease — so neither excludes the other, and on the directory path the group upsert commits in a separate transaction, so its row lock is already released. Without the lock, the removal set is computed against a snapshot a concurrent pass may have moved past, and a subject that pass inserted would survive an enumeration that never observed it: membership retained rather than revoked. Locking also closes the narrower window the blind delete already had between its delete and its commit. `created_at` on a member row now means first-observed rather than last-observed. No reader projects it, and the group's `lastSyncedAt` remains the freshness signal and is still written every pass. Also corrects the processing-queue TSDoc, which argued the per-tenant lane limits were sized rather than inherited and cited a concurrency figure derived from an unsound measurement. * test(knowledge): assert the group lock precedes the membership read Presence of the lock was the only thing asserted, so moving it below the read would have kept the test green while reopening the stale-snapshot race the lock exists to close. The assertion is now the relative call order of the lock and the member-table read. --- apps/sim/background/knowledge-processing.ts | 28 +++--- .../connectors/external-group-sync.test.ts | 93 ++++++++++++++++++- .../connectors/external-group-sync.ts | 60 +++++++++++- 3 files changed, 165 insertions(+), 16 deletions(-) diff --git a/apps/sim/background/knowledge-processing.ts b/apps/sim/background/knowledge-processing.ts index 8ca8526ae26..3e8a6c2f81e 100644 --- a/apps/sim/background/knowledge-processing.ts +++ b/apps/sim/background/knowledge-processing.ts @@ -211,20 +211,24 @@ export async function runDocumentProcessing( /** * Both lanes are keyed by tenant at dispatch, so `concurrencyLimit` is the - * ceiling one tenant may hold in that lane, not a ceiling for the fleet. The - * shared bound is the Trigger.dev environment concurrency limit, which is where - * a global ceiling belongs; observed peak there is ~97 across every task. + * ceiling one tenant may hold in that lane, not a ceiling for the fleet. There + * is no longer a fleet-wide ceiling for document processing: the aggregate is + * active tenants times the lane limit, bounded only by the Trigger.dev + * environment concurrency limit, which every other task shares. * - * Both default to the limit the single shared queue carried, which is what - * keeps this split from ever draining slower than the queue it replaces: the - * busiest case it has to beat is one tenant alone, and one tenant alone still - * gets the same slots it used to get for backfill plus a separate allowance for - * work someone is waiting on. Any second tenant is pure gain, because under the - * shared queue it got whatever the first one left. + * Both carry 20 because that is the number the single shared queue carried, not + * because 20 was derived for a per-tenant ceiling — it has been the default + * since the queue was introduced and the split changed its unit rather than its + * value. One tenant alone therefore still gets what it used to for backfill, + * plus a separate allowance for work someone is waiting on; two tenants draw + * twice the aggregate the shared queue ever allowed. * - * Splitting the two into separate variables is for operating them, not for - * sizing them: backfill is the one to lower when the environment ceiling is the - * binding constraint, and lowering it must not slow down a person's upload. + * So backfill is the one to lower, and the database is what decides when: it is + * the resource the aggregate actually lands on, and the per-document embedding + * writes are the load. Lower it when their latency climbs, not when the + * Trigger.dev environment limit is approached. The queue concurrency override + * API applies a new value without a redeploy; this variable is read when the + * worker deploy registers the queue, so changing it here needs one. */ export const interactiveProcessingQueue = queue({ name: INTERACTIVE_PROCESSING_QUEUE_NAME, diff --git a/apps/sim/lib/knowledge/connectors/external-group-sync.test.ts b/apps/sim/lib/knowledge/connectors/external-group-sync.test.ts index 82082479983..d95ff2551b0 100644 --- a/apps/sim/lib/knowledge/connectors/external-group-sync.test.ts +++ b/apps/sim/lib/knowledge/connectors/external-group-sync.test.ts @@ -124,6 +124,89 @@ describe('syncExternalDirectoryGroups', () => { ) }) + /** + * The reason membership writes are a diff: a directory sync overwhelmingly + * re-observes membership that has not changed, and rewriting the group would + * charge two row writes per member to autovacuum for no change at all. + */ + it('writes nothing when the observed membership already matches', async () => { + queueTableRows(schemaMock.knowledgeExternalGroup, []) + queueTableRows(schemaMock.knowledgeExternalGroupMember, [{ subjectToken: 'u:alice@corp.com' }]) + const dir = directory({ + listGroups: vi.fn(async () => [{ id: 'eng@corp.com' }]), + listGroupMembers: vi.fn(async (group) => ({ + group, + memberTokens: ['u:alice@corp.com'], + complete: true, + })), + }) + + await syncExternalDirectoryGroups({ workspaceId: 'ws-1', directory: dir }) + + expect(dbChainMockFns.delete).not.toHaveBeenCalled() + expect(dbChainMockFns.values).not.toHaveBeenCalledWith( + expect.arrayContaining([expect.objectContaining({ subjectToken: 'u:alice@corp.com' })]) + ) + expect(dbChainMockFns.set.mock.calls.some(([value]) => 'lastSyncedAt' in value)).toBe(true) + }) + + /** + * The removal set is computed from a read, so that read has to be serialized + * against the other writer. Both callers fence on different leases, and the + * directory path commits its group upsert in a separate transaction, so the + * lock has to be taken here. + */ + it('locks the group row before reading the membership it will diff against', async () => { + queueTableRows(schemaMock.knowledgeExternalGroup, []) + queueTableRows(schemaMock.knowledgeExternalGroupMember, [{ subjectToken: 'u:alice@corp.com' }]) + const dir = directory({ + listGroups: vi.fn(async () => [{ id: 'eng@corp.com' }]), + listGroupMembers: vi.fn(async (group) => ({ + group, + memberTokens: ['u:alice@corp.com'], + complete: true, + })), + }) + + await syncExternalDirectoryGroups({ workspaceId: 'ws-1', directory: dir }) + + expect(dbChainMockFns.for).toHaveBeenCalledWith('update') + const memberReadIndex = dbChainMockFns.from.mock.calls.findIndex( + ([table]) => table === schemaMock.knowledgeExternalGroupMember + ) + expect(memberReadIndex).toBeGreaterThanOrEqual(0) + /** + * Ordering is the property, not the presence: a lock taken after the read + * leaves exactly the stale-snapshot race it exists to close. + */ + expect(dbChainMockFns.for.mock.invocationCallOrder[0]).toBeLessThan( + dbChainMockFns.from.mock.invocationCallOrder[memberReadIndex] + ) + }) + + it('writes only the difference when membership changed', async () => { + queueTableRows(schemaMock.knowledgeExternalGroup, []) + queueTableRows(schemaMock.knowledgeExternalGroupMember, [ + { subjectToken: 'u:alice@corp.com' }, + { subjectToken: 'u:bob@corp.com' }, + ]) + const dir = directory({ + listGroups: vi.fn(async () => [{ id: 'eng@corp.com' }]), + listGroupMembers: vi.fn(async (group) => ({ + group, + memberTokens: ['u:alice@corp.com', 'u:carol@corp.com'], + complete: true, + })), + }) + + await syncExternalDirectoryGroups({ workspaceId: 'ws-1', directory: dir }) + + expect(dbChainMockFns.values).toHaveBeenCalledWith([ + { groupId: expect.any(String), subjectToken: 'u:carol@corp.com' }, + ]) + expect(dbChainMockFns.delete).toHaveBeenCalledTimes(1) + }) + it.each(['ws', 'pub', 'link', 'g:confluence:cloud:group', 'alice@corp.com', 'u:Alice@corp.com'])( 'rejects invalid member %s before replacing membership or updating freshness', async (invalid) => { @@ -370,7 +453,15 @@ describe('refreshConnectorDirectory', () => { expect(dbChainMockFns.set.mock.calls.some(([value]) => 'lastCompleteSyncAt' in value)).toBe( false ) - expect(dbChainMockFns.delete).toHaveBeenCalledTimes(1) + /** + * The accessible group's membership is written; the denied one is left + * alone. Nothing is deleted because the diff found no member to remove — + * membership writes are the difference, not a full rewrite. + */ + expect(dbChainMockFns.values).toHaveBeenCalledWith([ + { groupId: expect.any(String), subjectToken: 'u:alice@corp.com' }, + ]) + expect(dbChainMockFns.delete).not.toHaveBeenCalled() }) it('keeps unknown failures blocking even when other group memberships refreshed', async () => { diff --git a/apps/sim/lib/knowledge/connectors/external-group-sync.ts b/apps/sim/lib/knowledge/connectors/external-group-sync.ts index a5b24273f58..9313a3551a2 100644 --- a/apps/sim/lib/knowledge/connectors/external-group-sync.ts +++ b/apps/sim/lib/knowledge/connectors/external-group-sync.ts @@ -353,7 +353,23 @@ export async function persistExternalGroupMembership( ) } -/** Membership replacement and its freshness watermark commit together. */ +/** + * Membership replacement and its freshness watermark commit together. + * + * Writes only the difference. Rewriting a whole group per sync costs two row + * writes per member every time, and a directory sync overwhelmingly re-observes + * membership that has not changed. Unconditional replacement had taken this + * table to ~55M lifetime inserts and ~55M deletes against ~127k live rows — + * roughly 430x write amplification, holding it at ~91% dead tuples through + * 2,447 autovacuum cycles, an order of magnitude more than any comparable + * table. That vacuum load is charged to the same I/O every other query on the + * instance competes for. The extra read is one index scan of the group's + * primary-key prefix, and it is what lets an unchanged group write nothing. + * + * `created_at` therefore becomes first-observed rather than last-observed. No + * reader projects it; the group's own `lastSyncedAt` below is the freshness + * signal, and it is still written every pass. + */ async function replaceGroupMembers( groupId: string, memberTokens: string[], @@ -364,13 +380,51 @@ async function replaceGroupMembers( if (memberTokens.some((token) => !isDirectoryMemberToken(token, directory))) { throw new Error('Directory membership contains an invalid identity token') } + /** + * Serializes membership writes for this group before the set is read. + * + * The two callers fence on different leases — the directory lease and the + * connector sync lease — so neither excludes the other, and on the directory + * path the group upsert commits in a separate transaction from this one, so + * its row lock is already gone. Without this, the removal set is computed + * from a snapshot a concurrent pass may have moved past, and a subject that + * pass inserted would survive a complete enumeration that did not observe it: + * membership retained rather than revoked. The blind delete this replaced was + * immune because it never read first. + */ await tx - .delete(knowledgeExternalGroupMember) + .select({ id: knowledgeExternalGroup.id }) + .from(knowledgeExternalGroup) + .where(eq(knowledgeExternalGroup.id, groupId)) + .for('update') + const desired = new Set(memberTokens) + const existing = await tx + .select({ subjectToken: knowledgeExternalGroupMember.subjectToken }) + .from(knowledgeExternalGroupMember) .where(eq(knowledgeExternalGroupMember.groupId, groupId)) - for (const batch of chunkArray([...new Set(memberTokens)], MEMBER_WRITE_BATCH_SIZE)) { + const retained = new Set() + const removed: string[] = [] + for (const row of existing) { + if (desired.has(row.subjectToken)) retained.add(row.subjectToken) + else removed.push(row.subjectToken) + } + const added = [...desired].filter((subjectToken) => !retained.has(subjectToken)) + + for (const batch of chunkArray(removed, MEMBER_WRITE_BATCH_SIZE)) { + await tx + .delete(knowledgeExternalGroupMember) + .where( + and( + eq(knowledgeExternalGroupMember.groupId, groupId), + inArray(knowledgeExternalGroupMember.subjectToken, batch) + ) + ) + } + for (const batch of chunkArray(added, MEMBER_WRITE_BATCH_SIZE)) { await tx .insert(knowledgeExternalGroupMember) .values(batch.map((subjectToken) => ({ groupId, subjectToken }))) + .onConflictDoNothing() } await tx .update(knowledgeExternalGroup)